{
  "slug": "docker-deployment",
  "title": "Docker Deployment",
  "description": "Deploy @webhouse/cms with Docker — two options: pre-built image from GHCR, or custom Dockerfile for your site.",
  "category": "deployment",
  "order": 1,
  "locale": "en",
  "translationGroup": "c71102e1-e175-454f-a9d5-7171cde71cf8",
  "helpCardId": null,
  "content": "The CMS admin is available as a pre-built Docker image on GitHub Container Registry. There are two ways to run it:\n\n- **Option A: Pre-built image** — pull and run in 2 minutes, includes a demo site out of the box\n- **Option B: Custom Dockerfile** — build your own image with site + CMS bundled together\n\n---\n\n## Option A: Pre-built image (recommended)\n\nThe fastest way to get started. The image includes a complete demo site (18 documents, English + Danish) so you can explore the CMS immediately.\n\n### Prerequisites\n\n- Docker installed on your machine or server\n- That's it.\n\n### Run it\n\n```bash\ndocker run -d \\\n  --name cms \\\n  -p 3010:3010 \\\n  -e ADMIN_EMAIL=you@example.com \\\n  -e ADMIN_PASSWORD=your-secure-password \\\n  ghcr.io/webhousecode/cms-admin:latest\n```\n\nOpen [http://localhost:3010](http://localhost:3010) and log in.\n\nDocker pulls the image automatically on first run. On first boot, the CMS seeds a demo site:\n\n```\n✦ First boot — seeding CMS Demo site...\n✓ CMS Demo site ready (18 documents, EN + DA)\n✓ Open http://localhost:3010 to get started\n```\n\n### Explore the demo\n\nThe demo site includes:\n- **3 collections:** Pages, Posts, Globals\n- **18 documents** in English and Danish\n- **i18n** with translation groups and locale switcher\n- **Rich text** content with headings, links, and formatting\n\nEdit content, create new documents, try AI features — everything works out of the box.\n\n### Step 3: Connect your own content (optional)\n\nWhen you're ready to use your own site, you have two options:\n\n**A) Mount a local directory:**\n```bash\ndocker run -d -p 3010:3010 \\\n  -v $(pwd)/my-site:/site \\\n  -e ADMIN_EMAIL=you@example.com \\\n  ghcr.io/webhousecode/cms-admin:latest\n```\n\n**B) Connect a GitHub repo** (recommended for production):\n\n1. Go to **Site Settings** in the admin\n2. Click **Add Site** and choose **GitHub adapter**\n3. Connect your GitHub account (OAuth)\n4. Select your repo containing `cms.config.ts`\n5. Content is now synced — edits in CMS push to GitHub, new containers pull from GitHub\n\n### How content persistence works\n\n![Docker container syncing with GitHub repo](/diagrams/docker-github-sync.svg)\n\n- **GitHub repo** is the source of truth — your content survives container restarts, upgrades, and redeployments\n- **Local cache** (`.cache/sites/{id}/`) gives fast reads — rebuilt automatically from GitHub on startup\n- **New `docker run`** = fresh container, empty cache, pulls content from GitHub within seconds\n- **Edits in CMS** write to GitHub immediately — every save is a Git commit\n\n### Step 4: Deploy to a cloud server\n\nThe same `docker run` command works on any server. For Fly.io:\n\n```bash\n# Install flyctl\ncurl -L https://fly.io/install.sh | sh\n\n# Create app in Stockholm (EU)\nfly apps create my-cms --region arn\n\n# Set secrets\nfly secrets set \\\n  ADMIN_EMAIL=you@example.com \\\n  ADMIN_PASSWORD=$(openssl rand -hex 16) \\\n  -a my-cms\n\n# Deploy the pre-built image directly\nfly deploy \\\n  --image ghcr.io/webhousecode/cms-admin:latest \\\n  --region arn \\\n  -a my-cms\n```\n\nYour CMS admin is now live at `https://my-cms.fly.dev`.\n\n### Upgrading\n\nTo upgrade to a new CMS version:\n\n```bash\n# Local\ndocker pull ghcr.io/webhousecode/cms-admin:latest\ndocker stop cms-admin && docker rm cms-admin\ndocker run -d --name cms-admin -p 3010:3010 \\\n  -e ADMIN_EMAIL=you@example.com \\\n  -e ADMIN_PASSWORD=your-password \\\n  ghcr.io/webhousecode/cms-admin:latest\n\n# Fly.io\nfly deploy --image ghcr.io/webhousecode/cms-admin:latest -a my-cms\n```\n\nYour content is safe in GitHub — the new container pulls it automatically.\n\n---\n\n## Option B: Custom Dockerfile\n\nFor when you want CMS admin + your site bundled in one container, or need custom build steps.\n\n### Prerequisites\n\n- A site project with `cms.config.ts` (create one with `npm create @webhouse/cms`)\n- Docker installed\n\n### Step 1: Create a site\n\n```bash\nnpm create @webhouse/cms my-site\ncd my-site\n```\n\n### Step 2: Add a Dockerfile\n\nCreate `Dockerfile` in your site's root:\n\n```dockerfile\n# ── Build CMS admin ──\nFROM node:22-alpine AS cms\nRUN corepack enable && corepack prepare pnpm@10 --activate\nWORKDIR /build\n\n# Clone CMS monorepo and build admin\nRUN apk add --no-cache git python3 make g++ \\\n && git clone --depth 1 https://github.com/webhousecode/cms.git . \\\n && pnpm install --frozen-lockfile \\\n && pnpm --filter @webhouse/cms build \\\n && pnpm --filter @webhouse/cms-ai build \\\n && pnpm --filter @webhouse/cms-mcp-client build \\\n && pnpm --filter @webhouse/cms-mcp-server build \\\n && pnpm --filter @webhouse/cms-admin build\n\n# ── Build your site ──\nFROM node:22-alpine AS site\nWORKDIR /build\nCOPY package*.json ./\nRUN npm ci\nCOPY . .\nRUN npm run build\n\n# ── Runner ──\nFROM node:22-alpine\nRUN apk add --no-cache libc6-compat\nWORKDIR /app\nENV NODE_ENV=production\n\n# CMS Admin (standalone)\nCOPY --from=cms /build/packages/cms-admin/.next/standalone ./admin/\nCOPY --from=cms /build/packages/cms-admin/.next/static ./admin/packages/cms-admin/.next/static\nCOPY --from=cms /build/packages/cms-admin/public ./admin/packages/cms-admin/public\n\n# Your site\nCOPY --from=site /build ./site/\n\n# Content + config\nCOPY cms.config.ts ./\nCOPY content/ ./content/\n\n# Start script\nRUN printf '#!/bin/sh\\ncd /app/admin && CMS_CONFIG_PATH=/app/cms.config.ts PORT=3010 node packages/cms-admin/server.js &\\ncd /app/site && PORT=3000 node server.js &\\nwait\\n' > /start.sh && chmod +x /start.sh\n\nEXPOSE 3000 3010\nCMD [\"/start.sh\"]\n```\n\n### Step 3: Build and run\n\n```bash\ndocker build -t my-site .\ndocker run -d -p 3000:3000 -p 3010:3010 \\\n  -e ADMIN_EMAIL=you@example.com \\\n  -e ADMIN_PASSWORD=your-password \\\n  my-site\n```\n\n- Site: `http://localhost:3000`\n- CMS Admin: `http://localhost:3010`\n\n### Step 4: Deploy to Fly.io\n\nCreate `fly.toml`:\n\n```toml\napp = \"my-site\"\nprimary_region = \"arn\"\n\n[build]\n  dockerfile = \"Dockerfile\"\n\n[http_service]\n  internal_port = 3000\n  force_https = true\n  auto_stop_machines = \"stop\"\n  auto_start_machines = true\n\n[[vm]]\n  memory = \"512mb\"\n  cpu_kind = \"shared\"\n  cpus = 1\n```\n\n```bash\nfly launch --no-deploy\nfly secrets set ADMIN_EMAIL=you@example.com\nfly secrets set ADMIN_PASSWORD=$(openssl rand -hex 16)\nfly deploy --now\n```\n\n### Content persistence in Option B\n\nWith the custom Dockerfile, content is baked into the image at build time. For persistence between deploys:\n\n- **Use a Fly.io volume** to mount `/app/content` as persistent storage\n- **Or use the GitHub adapter** (same as Option A) — content lives in your repo, not in the container\n- **Or enable cloud backup (F95)** — auto-backup to Cloudflare R2 or pCloud\n\n---\n\n## Environment variables\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `ADMIN_EMAIL` | First boot | Admin account email |\n| `ADMIN_PASSWORD` | First boot | Admin account password (generated if omitted) |\n| `CMS_CONFIG_PATH` | Option A | Path to cms.config.ts (default: `/site/cms.config.ts`) |\n| `ANTHROPIC_API_KEY` | AI features | Claude API key for AI writing, proofreading, translation |\n| `GITHUB_TOKEN` | GitHub adapter | Fine-grained PAT or OAuth token |\n\n---\n\n## Auto-created admin account\n\nOn first boot, if `ADMIN_EMAIL` is set and no users exist, the CMS auto-creates an admin account:\n\n```\n✓ Admin account created\n  Email: you@example.com\n  Password: a1b2c3d4e5f6...\n  ⚠ Change this after first login!\n```\n\nIf `ADMIN_PASSWORD` is not set, a random password is generated and printed to the container logs:\n\n```bash\n# View the generated password\ndocker logs cms-admin | grep Password\n\n# Or on Fly.io\nfly logs -a my-cms | grep Password\n```\n\n---\n\n## Available image tags\n\n| Tag | Description |\n|-----|-------------|\n| `latest` | Most recent stable release |\n| `0.2.15` | Specific version (matches npm package version) |\n\n```bash\n# Pull specific version\ndocker pull ghcr.io/webhousecode/cms-admin:0.2.15\n\n# Always use latest\ndocker pull ghcr.io/webhousecode/cms-admin:latest\n```\n\n---\n\n## Quick reference\n\n| Task | Command |\n|------|---------|\n| Pull latest | `docker pull ghcr.io/webhousecode/cms-admin:latest` |\n| Run locally | `docker run -d -p 3010:3010 -e ADMIN_EMAIL=me@x.com ghcr.io/webhousecode/cms-admin` |\n| View logs | `docker logs cms-admin` |\n| Stop | `docker stop cms-admin` |\n| Upgrade | `docker pull ...latest && docker stop && docker rm && docker run ...` |\n| Deploy to Fly.io | `fly deploy --image ghcr.io/webhousecode/cms-admin:latest` |",
  "excerpt": "The CMS admin is available as a pre-built Docker image on GitHub Container Registry. There are two ways to run it:\n\n- Option A: Pre-built image — pull and run in 2 minutes, includes a demo site out of the box\n- Option B: Custom Dockerfile — build your own image with site + CMS bundled together\n\n---\n",
  "seo": {
    "metaTitle": "Docker Deployment — webhouse.app Docs",
    "metaDescription": "Deploy @webhouse/cms with Docker — pre-built GHCR image or custom Dockerfile. Step-by-step guide for local and cloud deployment."
  },
  "createdAt": "2026-03-30T12:31:03.308Z",
  "updatedAt": "2026-03-31T21:00:00.000Z"
}