# Docker Guide This repository ships with a production Dockerfile for self-hosted or operator-managed deployments. ## What the Docker Image Contains - built Nuxt/Nitro server output - generated `.contentrain` assets required by the UI string/content client - `git` in the runtime container for repository operations - non-root `studio` user The container listens on port `3000`. ## Pre-Built Images (Recommended for Production) Official container images are published to GHCR on every `v*` tag by the release workflow. ```bash # Replace vX.Y.Z with the newest tag from https://github.com/Contentrain/studio/releases docker pull ghcr.io/contentrain/studio:vX.Y.Z ``` Available image tags for each release: - `:vX.Y.Z` and `:vX.Y.Z-prerelease.N` — exact release pin (recommended for production) - `:sha-` — immutable commit pin - `:X.Y`, `:X`, `:latest` — floating tags, stable releases only Prereleases do **not** publish `:latest`. See [RELEASING.md](RELEASING.md) for the full image-tag policy. Self-hosters should pin to an exact `vX.Y.Z` tag rather than `:latest` so upgrades are explicit. ## Build the Image Locally ```bash docker build -t contentrain-studio --build-arg SOURCE_COMMIT="$(git rev-parse HEAD)" . ``` Use this path for local iteration, forks, and patched builds. Production self-hosts should prefer the pre-built GHCR image above. `SOURCE_COMMIT` is optional. `.git` is excluded from the build context, so it is the only way the build can learn which commit it was made from; `/about` and `GET /api/health` report the version from `package.json` together with that commit (`v0.3.0 (1a2b3c4)`). Without it they show the version alone. Railway builds pick up `RAILWAY_GIT_COMMIT_SHA` on their own; the release workflow passes `SOURCE_COMMIT` for the GHCR images. Either value can also be overridden at run time with `NUXT_PUBLIC_BUILD_COMMIT` / `NUXT_PUBLIC_BUILD_VERSION`. ## Run the Image ```bash docker run \ --name contentrain-studio \ --env-file .env \ -p 3000:3000 \ contentrain-studio ``` Health endpoint: - `GET /api/health` — `{ status, timestamp, version, commit }`; assert on `version`/`commit` to confirm a deploy landed ## Required Runtime Secrets At minimum, production requires `NUXT_SESSION_SECRET` plus one provider pair: Supabase pair (default): - `NUXT_SUPABASE_URL` - `NUXT_SUPABASE_SERVICE_ROLE_KEY` - `NUXT_SUPABASE_ANON_KEY` Managed + postgres pair (plain PostgreSQL — set `NUXT_AUTH_PROVIDER=managed`, `NUXT_DATABASE_PROVIDER=postgres`): - `NUXT_POSTGRES_URL` - `NUXT_AUTH_JWT_SECRET` - `NUXT_SESSION_PASSWORD` - `NUXT_OAUTH_GITHUB_CLIENT_ID` / `NUXT_OAUTH_GITHUB_CLIENT_SECRET` — the GitHub App's client credentials (not a separate OAuth App) - `NUXT_RESEND_API_KEY` Depending on enabled surfaces, you may also need: - GitHub App credentials - Anthropic API key - Resend sender configuration - Stripe keys - R2 credentials - Redis URL See [../.env.example](../.env.example) and [SELF_HOSTING.md](SELF_HOSTING.md). ## Operational Notes - Persist your environment outside the image - Use `rediss://` for Redis when crossing untrusted networks - Terminate TLS at a reverse proxy or platform load balancer - Keep the container stateless; database, object storage, Redis, and SMTP stay external - If you rotate `NUXT_SESSION_SECRET`, also set `NUXT_SESSION_SECRET_PREVIOUS` during the migration window ## Example Reverse Proxy Expectations Your reverse proxy should: - forward HTTPS traffic to port `3000` - preserve `Host` and standard forwarding headers - set a sane request body limit for uploads - avoid caching authenticated application routes ## Release Check Before shipping a Docker deployment: ```bash pnpm release:check ``` The full release flow, image-tag policy, and GitHub Actions automation are documented in [RELEASING.md](RELEASING.md).