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) on every commit.

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.

The .npmrc requirement

Toast uses TipTap Pro packages from a private registry. .npmrc configures authentication:

@tiptap-pro:registry=https://registry.tiptap.dev/
//registry.tiptap.dev/:_authToken=${TIPTAP_PRO_TOKEN}

Both Dockerfiles pass the token as a build arg:

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
ARG TIPTAP_PRO_TOKEN
RUN pnpm install --frozen-lockfile

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

export TIPTAP_PRO_TOKEN=<your-token>

# API
docker build --build-arg TIPTAP_PRO_TOKEN="$TIPTAP_PRO_TOKEN" -t toast-api .

# Admin (VITE_API_URL is baked into the static bundle at build time)
docker build --build-arg TIPTAP_PRO_TOKEN="$TIPTAP_PRO_TOKEN" \
  --build-arg VITE_API_URL="https://your-api-url.com" \
  -f apps/admin/Dockerfile.admin -t toast-admin .

# Docs
docker build --build-arg TIPTAP_PRO_TOKEN="$TIPTAP_PRO_TOKEN" \
  -f apps/docs/Dockerfile.docs -t toast-docs .

Required Environment Variables

API Service:

VariablePurpose
TIPTAP_PRO_TOKENNPM registry auth (build time)
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
TIPTAP_PRO_TOKENNPM registry auth (build time)
VITE_API_URLAPI endpoint for frontend (baked at build time)

Docs Service:

VariablePurpose
TIPTAP_PRO_TOKENNPM registry auth (build time)

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 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
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" — 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.

On this page