Stateless Ghost CMS Architecture: Solving Persistent Image Loss and Speed Penalties via Cloudflare R2
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).
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`.
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:
- Core Web Vitals & LCP Speed: Moving static assets to the Cloudflare Edge network (`cdn.ypym.app`) frees the main site application server from delivering large files. Cover image latency decreased from a 1.2-second average to under 150ms globally. This improves the Largest Contentful Paint (LCP) score and overall performance metrics.
- Brand Equity & Google Image Indexing: By using a branded domain (`cdn.ypym.app`) instead of generic cloud endpoints, all assets within *Ink & Thought* and our *Company Press Release* channels are indexed directly under our own domain authority, building long-term link integrity and image search authority.
- Zero-Downtime Siaran Pers (Press Releases): Our corporate communications are now highly reliable. Infrastructure reboots and server migrations will never trigger broken images on historical press statements, ensuring a premium and consistent brand image for investors and partners.
"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