YPYM Logo YPYM INDONESIA | PT ADI TJANDA TEKNOLOGI
‹ Back to HQ
🚀

Stateless Ghost CMS Architecture: Solving Persistent Image Loss and Speed Penalties via Cloudflare R2

👤 Authors
Developer Division, Rochman Maarif
📅 Date Published
2026-06-24T10:30:00+07:00
🔄 Last Modified
2026-06-24T10:30:00+07:00
🚦 Document Status
Public Release

When building high-performance digital ecosystems, one of the most critical challenges is balancing **infrastructure reliability** with **page speed optimization**. At YPYM (Your Page Your Money), we rely on Ghost CMS as the main engine for our editorial publication channels: the science article and blog feed Ink & Thought, and our corporate Company Press Release channel.

However, Ghost CMS's out-of-the-box configuration in containerized environments (Docker/Coolify) carries severe structural limitations. This case study details how we eliminated post-deployment image loss, optimized Core Web Vitals page speed metrics, bypassed custom S3 API configuration challenges, and resolved strict browser security policies.

1. The Core Issue: Stateless Container Lifecycles vs. Volatile Local Storage

By default, Ghost CMS stores all uploaded editorial images in the container's local directory: `/var/lib/ghost/content/images/`. This pattern is highly problematic when deployed inside containerized cloud platforms (such as Docker, Coolify, or Nixpacks).

🚨 THE STATELESS LIFECYCLE ISSUE

Docker containers are fundamentally ephemeral. Every time we deploy code updates, perform server maintenance, or trigger automatic container rebuilds in Coolify, the old container instance is destroyed and replaced with a fresh build. Consequently, all uploaded cover images and assets are wiped instantly, triggering 404 errors (broken images) for visitors.

Additionally, serving high-resolution images directly from the Node.js/Ghost CMS application server incurs unnecessary computation overhead. Every image request triggers disk I/O, spikes Time to First Byte (TTFB), and exhausts resources that the core app server should dedicate to rendering dynamic content. This directly degrades Core Web Vitals, particularly the **Largest Contentful Paint (LCP)** metric.

2. The Solution Architecture: Cloudflare R2 & Custom CDN

To solve these problems permanently, we decoupled the media storage tier from the application layer.

We selected Cloudflare R2 Object Storage as our media backend. Unlike traditional cloud object storage providers, Cloudflare R2 guarantees **Zero Egress Fees** (no data transfer charges for downloads). Since blog assets are fetched thousands of times by global visitors, offloading images to R2 avoids the unpredictable and compounding network costs of conventional S3 providers.

Now, when an editor uploads an image via the Ghost Admin panel (`ghost.ypym.app/ghost`), the storage adapter intercepts the operation and uploads the file directly to our R2 bucket, named `ypym-blog-images`. To deliver these assets, we configured a custom CDN subdomain: `https://cdn.ypym.app`.

// New Asset Delivery Pipeline
User Browser -> Request Image -> cdn.ypym.app (Cloudflare Edge Cache)
                         -> MISS -> Cloudflare R2 Bucket (ypym-blog-images)
                         -> HIT -> Served instantly from the nearest Edge Cache (<30ms)

3. Technical Challenges & Engineering Resolutions

While the architectural blueprint is straightforward, implementing it in production under Coolify introduced several low-level technical challenges that required custom resolutions:

Challenge A: Dependency Flattening & Volume Mount Conflicts

Initially, we attempted to install the `ghost-storage-adapter-s3` dependency manually using standard container terminal access. However, npm flattens dependencies into `/var/lib/ghost/current/node_modules/` or temporary folders. Because these directories sit outside our persistent mount (`/var/lib/ghost/content/`), the adapter files were completely wiped on rebuilds, causing `Cannot find module` runtime failures and launching the container into a fatal boot loop.

✔️ Resolution: We wrote an automation script within the container's custom entrypoint in Coolify. On boot, the script checks if the persistent directory /var/lib/ghost/content/adapters/storage/s3 is present. If missing, it initializes a local npm scope inside the persistent volume, installs the S3 adapter package and its dependencies, writes a custom index.js initialization entrypoint, and configures matching permissions. The stack is now fully persistent and self-healing.

Challenge B: Cloudflare R2 Virtual-Hosted Bucket Naming

By default, the AWS S3 SDK constructs bucket endpoints using virtual-hosted naming conventions (e.g. https://ypym-blog-images.<account-id>.r2.cloudflarestorage.com). This pattern is rejected by Cloudflare R2, leading to DNS lookup issues and SSL connection handshakes failures.

✔️ Resolution: We injected the environment variable storage__s3__forcePathStyle=true into the service. This forces the SDK client to route commands using path-style URIs (e.g. https://<account-id>.r2.cloudflarestorage.com/ypym-blog-images/), which are fully supported by Cloudflare R2.

Challenge C: Content Security Policy (CSP) Blocking

Once R2 S3 uploads were successfully working, we noticed visitor browsers still failed to load cover images, showing broken links instead. Reviewing the web console logs revealed a strict security block: *Content Security Policy (CSP) directive violation*. The Astro frontend middleware was blocking images from loading because our new `https://cdn.ypym.app` domain was not whitelisted.

✔️ Resolution: We modified the Astro server security filter in middleware.ts to whitelist https://cdn.ypym.app within the img-src and media-src directives. Pushing this change and redeploying the site allowed browsers to render the S3/R2 assets securely.

4. Impact on YPYM's SEO & Digital Strategy

Offloading image storage directly supports our long-term **SEO and Brand Experience** guidelines:

"Modern SEO infrastructure requires a clean separation of logical content and static media. Offloading blog assets to Cloudflare R2 protected by strict CSP parameters ensures long-term data integrity while delivering page load speeds without compromise."

— Developer Division, Rochman Maarif