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.
Quick start
Section titled “Quick start”# Clone the repositorygit clone https://github.com/wollycms/wollycms.gitcd wollycms
# Create your .env filecp .env.example .env# Edit .env and set a strong JWT_SECRETGenerate a JWT secret:
openssl rand -base64 32Add it to .env:
JWT_SECRET=your-generated-secret-hereSITE_URL=https://your-site.example.comCORS_ORIGINS=https://your-site.example.comStart the containers:
docker compose up -dWollyCMS is now running at http://localhost:4321.
docker-compose.yml
Section titled “docker-compose.yml”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:Volumes
Section titled “Volumes”| Volume | Container path | Purpose |
|---|---|---|
wolly-data | /app/data | SQLite database file |
wolly-uploads | /app/uploads | Uploaded media files |
Environment variables
Section titled “Environment variables”| Variable | Default | Description |
|---|---|---|
NODE_ENV | production | Environment mode |
PORT | 4321 | Server port |
HOST | 0.0.0.0 | Bind address |
DATABASE_URL | sqlite:./data/wolly.db | Database connection string |
MEDIA_DIR | ./uploads | Local media storage directory |
JWT_SECRET | required | Secret for JWT signing |
CORS_ORIGINS | * | Allowed CORS origins |
SITE_URL | http://localhost:4322 | Frontend URL (for sitemaps) |
RATE_LIMIT_AUTH | 10 | Max auth attempts per window |
RATE_LIMIT_WINDOW_MS | 900000 | Rate limit window (15 min) |
Using PostgreSQL
Section titled “Using PostgreSQL”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:Using S3/R2 for media
Section titled “Using S3/R2 for media”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.comReverse proxy
Section titled “Reverse proxy”Put WollyCMS behind a reverse proxy (Caddy, Nginx, Traefik) for TLS and custom domains:
# Caddyfilecms.example.com { reverse_proxy wollycms:4321}Health check
Section titled “Health check”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 }}Updating
Section titled “Updating”git pulldocker compose builddocker compose up -dThe container runs database migrations automatically on startup.