Skip to content

Docker

WollyCMS ships with a multi-stage Dockerfile and a ready-to-use docker-compose.yml. This is the recommended deployment method for self-hosted environments.

Terminal window
# Clone the repository
git clone https://github.com/wollycms/wollycms.git
cd wollycms
# Create your .env file
cp .env.example .env
# Edit .env and set a strong JWT_SECRET

Generate a JWT secret:

Terminal window
openssl rand -base64 32

Add it to .env:

Terminal window
JWT_SECRET=your-generated-secret-here
SITE_URL=https://your-site.example.com
CORS_ORIGINS=https://your-site.example.com

Start the containers:

Terminal window
docker compose up -d

WollyCMS is now running at http://localhost:4321.

services:
wollycms:
build: .
ports:
- "4321:4321"
environment:
- NODE_ENV=production
- PORT=4321
- HOST=0.0.0.0
- DATABASE_URL=sqlite:./data/wolly.db
- MEDIA_DIR=./uploads
- JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env}
- CORS_ORIGINS=${CORS_ORIGINS:-*}
- RATE_LIMIT_AUTH=${RATE_LIMIT_AUTH:-10}
- RATE_LIMIT_WINDOW_MS=${RATE_LIMIT_WINDOW_MS:-900000}
- SITE_URL=${SITE_URL:-http://localhost:4322}
volumes:
- wolly-data:/app/data
- wolly-uploads:/app/uploads
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:4321/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
volumes:
wolly-data:
wolly-uploads:
VolumeContainer pathPurpose
wolly-data/app/dataSQLite database file
wolly-uploads/app/uploadsUploaded media files
VariableDefaultDescription
NODE_ENVproductionEnvironment mode
PORT4321Server port
HOST0.0.0.0Bind address
DATABASE_URLsqlite:./data/wolly.dbDatabase connection string
MEDIA_DIR./uploadsLocal media storage directory
JWT_SECRETrequiredSecret for JWT signing
CORS_ORIGINS*Allowed CORS origins
SITE_URLhttp://localhost:4322Frontend URL (for sitemaps)
RATE_LIMIT_AUTH10Max auth attempts per window
RATE_LIMIT_WINDOW_MS900000Rate limit window (15 min)

To use PostgreSQL instead of SQLite, set the DATABASE_URL to a PostgreSQL connection string:

services:
wollycms:
environment:
- DATABASE_URL=postgresql://wolly:password@db:5432/wollycms
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: wolly
POSTGRES_PASSWORD: password
POSTGRES_DB: wollycms
volumes:
- pg-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U wolly"]
interval: 10s
timeout: 5s
retries: 5
volumes:
wolly-data:
wolly-uploads:
pg-data:

For production deployments, store media in S3-compatible storage instead of local volumes:

environment:
- MEDIA_STORAGE=s3
- S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
- S3_BUCKET=wollycms-media
- S3_REGION=us-east-1
- S3_ACCESS_KEY=${S3_ACCESS_KEY}
- S3_SECRET_KEY=${S3_SECRET_KEY}
- S3_PUBLIC_URL=https://cdn.example.com

Put WollyCMS behind a reverse proxy (Caddy, Nginx, Traefik) for TLS and custom domains:

# Caddyfile
cms.example.com {
reverse_proxy wollycms:4321
}

The health endpoint is available at /api/health:

{
"status": "ok",
"version": "0.1.0",
"uptime": 3600,
"timestamp": "2025-01-15T12:00:00.000Z",
"cache": { "entries": 42 }
}
Terminal window
git pull
docker compose build
docker compose up -d

The container runs database migrations automatically on startup.