Docker
How Docker builds work in Toast and how to avoid breaking them.
Overview
Toast has three Dockerfiles, each producing a multi-stage production image:
| File | Service | Purpose |
|---|---|---|
Dockerfile | API | Node.js Hono server with database access |
apps/admin/Dockerfile.admin | Admin | Vite static build served via nginx |
apps/docs/Dockerfile.docs | Docs | Next.js standalone build for documentation |
CI builds and pushes all three to GitHub Container Registry (ghcr.io) on every commit.
Node.js Version Policy
- Toast requires Node.js 24.x (
package.jsonengines) - CI, Docker images, and the devcontainer run Node.js 24
.nvmrcand.node-versionare pinned to24
Why Docker Builds Break
The most common cause is missing files or environment variables that pnpm needs during install.
No private registry (token-free builds)
Toast has no private-registry dependencies — Docker builds need no token or registry auth. The committed .npmrc is token-free and pnpm install --frozen-lockfile works as-is. The ARG TIPTAP_PRO_TOKEN in the Dockerfiles is now unused and may be left empty.
The editor's paid TipTap Pro extensions are decoupled and off by default (the editor degrades gracefully without them — see Local Dev Setup). To bake them into an image, your build must run ./scripts/enable-tiptap-pro.sh with a valid TIPTAP_PRO_TOKEN before pnpm install, so the @tiptap-pro/* overlay is installed.
API build scope
The API Dockerfile builds only the API workspace and its dependency graph:
RUN pnpm build --filter @toast/api...If the API image needs artifacts from packages outside this graph, make those packages real dependencies of @toast/api or widen the filter.
Docs build: postinstall order
fumadocs-mdx runs a postinstall that compiles source.config.ts via esbuild. This file and tsconfig.json must be copied into the deps stage before pnpm install, or the postinstall fails.
Building Locally
# API
docker build -t toast-api .
# Admin (VITE_API_URL is baked into the static bundle at build time)
docker build --build-arg VITE_API_URL="https://your-api-url.com" \
-f apps/admin/Dockerfile.admin -t toast-admin .
# Docs
docker build -f apps/docs/Dockerfile.docs -t toast-docs .No TIPTAP_PRO_TOKEN build arg is needed. To include the optional Pro extensions, run ./scripts/enable-tiptap-pro.sh (with a valid token) before building so the overlay is present in the build context.
Required Environment Variables
API Service:
| Variable | Purpose |
|---|---|
TIPTAP_COLLAB_SECRET | JWT signing for collaboration tokens |
TIPTAP_AI_SECRET | JWT signing for AI tokens |
VITE_TIPTAP_COLLAB_APP_ID | Returned in collaboration token response |
VITE_TIPTAP_AI_APP_ID | Returned in AI token response |
EMAIL_DRIVER | Email provider (required in production) |
EMAIL_FROM | Default sender address |
STORAGE_DRIVER | Storage backend driver package name |
S3_ENDPOINT | S3-compatible endpoint URL |
S3_ACCESS_KEY_ID | S3 access key ID |
S3_SECRET_ACCESS_KEY | S3 secret access key |
S3_BUCKET | S3 bucket name |
S3_PUBLIC_URL | Public URL for uploaded files |
Admin Service:
| Variable | Purpose |
|---|---|
VITE_API_URL | API endpoint for frontend (baked at build time) |
TIPTAP_PRO_TOKEN | Optional — only if enabling the TipTap Pro overlay (see above) |
Docs Service:
The docs image needs no build-time secrets (it has no private dependencies).
Seed Fixtures
The API Dockerfile copies seed fixtures into the final image for preview/staging environments:
COPY --from=build /app/shared/db/dist/fixtures ./shared/db/dist/fixturesThe seed script uses these at runtime when Railway's preDeployCommand runs pnpm db:seed.
Pre-built Images
CI pushes to ghcr.io on every commit:
docker pull ghcr.io/tryghost/toast-api:main
docker pull ghcr.io/tryghost/toast-admin:main
docker pull ghcr.io/tryghost/toast-docs:main| Tag | Description |
|---|---|
main | Latest from main branch (mutable) |
sha-<commit> | Specific commit (immutable) |
pr-<number> | PR preview build |
Troubleshooting
"ERR_PNPM_FETCH_404" or "No authorization header" — Token isn't reaching pnpm. Check: is .npmrc copied? Is the ARG defined before COPY? Is the build-arg being passed?
"Lockfile is not up to date" — Run pnpm install locally and commit the updated lockfile.
"Connect Timeout Error" — Transient network failure in CI. Retry the deployment.
Updating pnpm version — When you update packageManager in package.json, also update all three Dockerfiles' corepack prepare pnpm@X.Y.Z lines.