Getting Started

10 min readUpdated 27 Apr 2026

VeloCMS runs anywhere Docker runs — a $6/mo VPS, a Railway service, or your laptop. This guide walks you through the full self-hosting path: Docker Compose setup, environment variables, Cloudflare R2 for media, wildcard DNS, and creating your first admin account. By the end you'll have a running instance you can actually log into.

Prerequisites

  • Docker 24+ and Docker Compose v2
  • A domain name (or subdomain) you control
  • A Cloudflare account (free tier works) for R2 media storage
  • Optional but recommended: a Railway account for managed hosting

Docker Compose quickstart

The repo ships a docker-compose.yml that starts both the Next.js frontend and PocketBase backend together. Clone the repo, copy the env example, and you're one command away from a running instance.

terminal
git clone https://github.com/muharremyurtsever/velocms.git
cd velocms
cp .env.example .env.local
# Edit .env.local with your values (see below)
docker compose up
bash

Required environment variables

VeloCMS reads all configuration from environment variables — no config files, no magic. The .env.example in the root covers every required and optional variable. Here are the ones you must set before first launch:

.env.local
# Core
VELOCMS_MODE=single                # or "multi" for multi-tenant SaaS mode
POCKETBASE_URL=http://localhost:8090
POCKETBASE_ADMIN_EMAIL=[email protected]
POCKETBASE_ADMIN_PASSWORD=changeme123

# Public site URL (used for sitemap, OG images, canonical links)
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
NEXT_PUBLIC_PLATFORM_DOMAIN=yourdomain.com

# Encryption key — 64 hex characters (32 bytes). Generate with:
# openssl rand -hex 32
ENCRYPTION_KEY=<64-char-hex>

# Cloudflare R2 media storage
CLOUDFLARE_ACCOUNT_ID=<your-account-id>
CLOUDFLARE_API_TOKEN=<your-api-token>
R2_BUCKET_NAME=velocms-media
R2_PUBLIC_URL=https://pub-<hash>.r2.dev

# Email (Resend)
RESEND_API_KEY=re_...
RESEND_FROM_EMAIL=[email protected]

# AI writing assistant (optional — editor AI features require this)
GEMINI_API_KEY=AI...

# Billing — only required for membership/paywall features
STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
bash

Single-instance vs multi-tenant mode

VeloCMS ships in two modes controlled by a single env var. VELOCMS_MODE=single is what you want for a personal blog or a small agency running one site — everything lives in one PocketBase instance, dead simple. VELOCMS_MODE=multi is the SaaS mode: a master PocketBase handles auth and billing, and each tenant gets their own isolated PocketBase with their own content. Multi-mode is what velocms.org itself runs in production.

Featuresinglemulti
PocketBase instances11 master + N tenant
Tenant isolationnone (single blog)full DB isolation
Custom domainsnoyes (Cloudflare for SaaS)
Stripe billingoptionalper-tenant BYOK or platform Stripe
Best forpersonal blog, agency siteSaaS platform, blog hosting

Setting up Cloudflare R2

R2 is where all uploaded media lives — images, videos, file attachments. It's S3-compatible and has zero egress fees, which matters when you're hosting a photo-heavy blog. You'll create a bucket, generate an API token with object write permissions, and grab the public URL.

Cloudflare R2 setup steps
# 1. Log into https://dash.cloudflare.com → R2 Object Storage
# 2. Create a bucket: "velocms-media" (or any name you prefer)
# 3. Under bucket settings, enable Public Access → note the r2.dev URL
# 4. Create an API Token with:
#    - Object Read & Write permissions on your bucket
#    - Copy the token value — you only see it once
# 5. Set in .env.local:
CLOUDFLARE_ACCOUNT_ID=<from Cloudflare dashboard top-right>
CLOUDFLARE_API_TOKEN=<the token you just copied>
R2_BUCKET_NAME=velocms-media
R2_PUBLIC_URL=https://pub-<hash>.r2.dev
bash

Wildcard DNS setup

If you're running in multi-tenant mode, each tenant gets their own subdomain (e.g. alice.yourdomain.com). You'll need a wildcard DNS record pointing to your server, and Cloudflare configured to not proxy it (grey cloud). The apex domain stays orange cloud (Cloudflare-proxied).

Cloudflare DNS records
# Add these two records in Cloudflare DNS:
Type   Name             Content               Proxy status
A      yourdomain.com   <your-server-IP>      Proxied (orange cloud)
A      *.yourdomain.com <your-server-IP>      DNS only (grey cloud)

# The wildcard MUST be grey cloud — Cloudflare's proxy doesn't support
# wildcard certificates on free plans, and Railway handles TLS for subdomains.
bash

Creating your first admin account

Once Docker Compose is running, PocketBase exposes its admin UI at port 8090. The very first time you visit it, you'll be prompted to create a superuser — this is your admin account. After that, you can log into VeloCMS at /login with those credentials.

terminal
# Navigate to PocketBase admin (while Docker is running):
open http://localhost:8090/_/

# Create your superuser email + password when prompted.
# Then visit the VeloCMS admin panel:
open http://localhost:3000/login
bash

Deploying to Railway

Railway is the recommended production host — it handles Docker builds, zero-downtime deploys, automatic TLS, and custom domains out of the box. The repo includes a railway.json that wires both services (Next.js and PocketBase) into a single project.

terminal
# Install Railway CLI
npm install -g @railway/cli

# Login and link to your project
railway login
railway link

# Set production env vars (do this before the first deploy)
railway variables --set "VELOCMS_MODE=multi" --skip-deploys
railway variables --set "POCKETBASE_URL=https://your-pb.up.railway.app" --skip-deploys
railway variables --set "NEXT_PUBLIC_SITE_URL=https://yourdomain.com" --skip-deploys
# ... (set all other vars from .env.example)

# Deploy
git push origin main  # Railway auto-deploys on push
bash

That's it. Once the first deploy finishes, visit your Railway service URL to confirm everything is live. Check the build logs if anything looks wrong — Railway's log viewer is genuinely useful for tracing startup errors.