Deploy Boxcar — Payload CMS + Cloudflare R2
Self-hosted Payload + Next.js blog with Cloudflare R2 media.
Payload-CMS-Cloudflare-Railway-Template
Just deployed
Just deployed
Just deployed
/var/lib/postgresql/data
Deploy and Host Boxcar — Payload CMS + Cloudflare R2 on Railway
Boxcar is a production-ready blog template combining Payload CMS, Next.js 16, and Cloudflare R2 for media storage. Deploy a fully self-hosted, code-owned headless blog with a polished admin, scheduled publishing, draft workflows, and SEO baked in — without wiring up infrastructure yourself.
About Hosting Boxcar — Payload CMS + Cloudflare R2
Hosting Boxcar on Railway provisions three pieces: a Next.js web service running the Payload admin and public blog, a separate cron service that processes scheduled-publish jobs every 30 minutes, and a managed PostgreSQL database for content. Migrations run automatically before each deploy via the preDeployCommand hook, and Railway pings /healthz to confirm the app is healthy. Cloudflare R2 handles media uploads through Payload's S3-compatible storage plugin — you supply bucket credentials via environment variables. Push to main and Railway redeploys both services in lockstep.
Common Use Cases
- Personal or company blog with full code-level control over the admin and frontend
- Editorial publication with draft, autosave, and scheduled-publish workflows for multi-author teams
- Migration target for a WordPress site — every collection ships with
legacy.wpId/legacy.wpUrlfields and a stub WP-import script
Dependencies for Boxcar — Payload CMS + Cloudflare R2 Hosting
- A Cloudflare account with R2 enabled (for media uploads). The template runs without R2 — uploads just fall back to local storage — but production setups should wire it up.
- A GitHub fork or copy of the template repo, so Railway can auto-deploy on push.
Deployment Dependencies
- Payload CMS documentation
- Cloudflare R2 documentation
- Next.js 16 documentation
- Railway documentation
Implementation Details
Scheduled publishing is implemented entirely on Payload's publishedAt field plus a custom job. When an editor sets a future publishedAt and clicks Schedule Post, a beforeChange hook demotes the post to draft so it stays hidden, and an afterChange hook enqueues a publishScheduledPost job with waitUntil = publishedAt. The Cron service runs pnpm jobs:run every 30 minutes; when the post's time arrives, the handler flips _status back to published. The handler is idempotent — it re-checks publishedAt before flipping, so reschedules and manual publishes don't double-fire.
Why Deploy Boxcar — Payload CMS + Cloudflare R2 on Railway?
Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.
By deploying Boxcar — Payload CMS + Cloudflare R2 on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.
Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.
By deploying Boxcar — Payload CMS + Cloudflare R2 on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.
Template Content
Payload-CMS-Cloudflare-Railway-Template
sungkhum/Boxcar-Payload-CMS-Cloudflare-Railway-TemplateR2_BUCKET
Cloudflare R2 bucket name. Create one in Cloudflare dashboard → R2 → Create Bucket.
R2_ENDPOINT
R2 S3-compatible endpoint, e.g. https://<account_id>.r2.cloudflarestorage.com
R2_PUBLIC_URL
Public URL where uploaded media is served — either your R2 public bucket URL (https://pub-xxx.r2.dev) or a custom domain you've connected.
R2_ACCESS_KEY_ID
Access Key ID for an R2 API token. R2 → Manage API Tokens → Object Read & Write scoped to your bucket.
R2_SECRET_ACCESS_KEY
Secret half of that R2 API token (shown only once at token creation).
PAYLOAD_SECRET
Must match PAYLOAD_SECRET on the app service so both processes share the same signing key.
