Toast
Contributor

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:

FileServicePurpose
DockerfileAPINode.js Hono server with database access
apps/admin/Dockerfile.adminAdminVite static build served via nginx
apps/docs/Dockerfile.docsDocsNext.js standalone build for documentation

CI builds and pushes all three to GitHub Container Registry (ghcr.io) for main pushes and same-repository pull requests after the prerequisite checks pass. The required CI check waits for all three builds and fails if any build fails. Fork pull requests skip publishing but must still pass every prerequisite check.

Node.js Version Policy

  • Toast requires Node.js 24.x (package.json engines)
  • CI, Docker images, and the devcontainer run Node.js 24
  • .nvmrc and .node-version are pinned to 24

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.

Workspace installs

Every install stage copies all workspace manifests so frozen installs can validate the complete lockfile. Filters select the service and its dependencies; build stages also install root build tools. The API production stage installs only its runtime dependency graph. When adding a workspace, add its manifest to all three Dockerfiles, including the API production stage.

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:

VariablePurpose
TIPTAP_COLLAB_SECRETJWT signing for collaboration tokens
TIPTAP_AI_SECRETJWT signing for AI tokens
VITE_TIPTAP_COLLAB_APP_IDReturned in collaboration token response
VITE_TIPTAP_AI_APP_IDReturned in AI token response
EMAIL_DRIVEREmail provider (required in production)
EMAIL_FROMDefault sender address
STORAGE_DRIVERStorage backend driver package name
S3_ENDPOINTS3-compatible endpoint URL
S3_ACCESS_KEY_IDS3 access key ID
S3_SECRET_ACCESS_KEYS3 secret access key
S3_BUCKETS3 bucket name
S3_PUBLIC_URLPublic URL for uploaded files

Admin Service:

VariablePurpose
VITE_API_URLAPI endpoint for frontend (baked at build time)
TIPTAP_PRO_TOKENOptional — 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/fixtures

The seed script uses these at runtime when Railway's preDeployCommand runs pnpm db:seed.

Pre-built Images

CI pushes to ghcr.io for main pushes and same-repository pull requests:

docker pull ghcr.io/tryghost/toast-api:main
docker pull ghcr.io/tryghost/toast-admin:main
docker pull ghcr.io/tryghost/toast-docs:main
TagDescription
mainLatest 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" — If the error names a missing workspace, check the manifest copies in the Dockerfile. If a manifest has changed, run pnpm install locally and commit the updated lockfile.

"Connect Timeout Error" — Transient network failure in CI. Retry the deployment.

Updating pnpm version — Docker uses Corepack to install the version declared in the root package.json automatically.

On this page