Toast
Developer

Self-Hosting

Deploy Toast with Railway, Docker, or manually.

Toast runs as three services — an API server, an admin panel, and a documentation site — backed by PostgreSQL. You can deploy all three together or individually.

Runtime Topology

                  ┌──────────────┐
  Browser ──────► │  Admin (nginx)│ :80
                  └──────┬───────┘
                         │ fetch

                  ┌──────────────┐      ┌────────────┐
  Browser ──────► │  API (Hono)  │ ───► │ PostgreSQL │
                  └──────────────┘      └────────────┘
                       :3000                :5432

                  ┌──────────────┐
  Browser ──────► │ Docs (Next.js)│ :3000
                  └──────────────┘
  • API — Hono server handling authentication, content management, uploads, and the public content API
  • Admin — Static React build served by nginx, communicates with the API via fetch
  • Docs — Next.js standalone server, no database connection required
  • PostgreSQL — Single database shared by the API

Railway provides the simplest deployment path with automatic PR preview environments, managed PostgreSQL, and zero Docker knowledge required.

See the full Railway Setup Guide for step-by-step instructions covering:

  • Provisioning all three services from the GitHub repo
  • Adding a managed PostgreSQL database
  • Configuring environment variables with Railway's interpolation syntax
  • Setting up PR preview environments
  • Custom domains and cross-site cookie configuration

Estimated setup time: 15-20 minutes.

Docker

Toast provides pre-built Docker images and Dockerfiles for all three services.

Pre-Built Images

CI publishes images to GitHub Container Registry on every commit:

# Authenticate with GHCR (images are currently private)
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# Pull latest images
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

Running with Docker

# API server (provide your env vars via --env-file)
docker run -p 3000:3000 --env-file .env ghcr.io/tryghost/toast-api:main

# Admin panel (pre-built images use relative API URLs)
docker run -p 8080:80 ghcr.io/tryghost/toast-admin:main

# Docs site (no env vars needed at runtime)
docker run -p 3300:3000 ghcr.io/tryghost/toast-docs:main

Pass secrets via --env-file rather than -e flags. The -e flag exposes values in ps output and shell history. Use chmod 600 .env to restrict file permissions.

Building Locally

If you need custom configuration (e.g., a different VITE_API_URL for the admin panel):

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 JS bundle at build time)
docker build \
  --build-arg TIPTAP_PRO_TOKEN="$TIPTAP_PRO_TOKEN" \
  --build-arg VITE_API_URL="https://api.example.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 .

Docker Compose

For local self-hosting, the repository includes a docker-compose.yml that runs the API and PostgreSQL:

# Start API + PostgreSQL
docker compose up -d

# Check logs
docker compose logs -f api

For the full Docker reference, including troubleshooting and CI integration, see Docker Patterns.

Manual Deployment

If you're not using Docker, you can build and run the services directly.

Prerequisites

  • Node.js 24.x
  • pnpm 10.x
  • PostgreSQL 15+

Build the API

git clone https://github.com/TryGhost/Toast.git
cd Toast
pnpm install

# Build the API and its workspace dependencies
pnpm build --filter @toast/api...

# Run database migrations
pnpm db:migrate

# Start the API server
node apps/api/dist/index.js

Build the Admin Panel

# Set the API URL (baked into the static bundle)
export VITE_API_URL=https://api.example.com

pnpm build --filter @toast/admin

# Serve the static files from apps/admin/dist/
# Use any static file server (nginx, caddy, etc.)

Build the Docs Site

pnpm build --filter @toast/docs

# Start the Next.js standalone server
node apps/docs/.next/standalone/apps/docs/server.js

Environment Variables

Required for All Deployments

VariablePurposeExample
DATABASE_URLPostgreSQL connection stringpostgresql://user:pass@host:5432/toast
BETTER_AUTH_SECRETAuth token signing (min 32 chars)openssl rand -base64 32
BETTER_AUTH_URLBase URL for auth callbackshttps://api.example.com
ADMIN_URLAdmin panel URL (CORS, trusted origin)https://admin.example.com
TIPTAP_PRO_TOKENNPM registry auth (build time only)Get from TipTap Cloud

Email (Required in Production)

VariablePurposeExample
EMAIL_DRIVEREmail provider package nametoast-driver-email-mailgun
EMAIL_FROMDefault sender addressnoreply@example.com
MAILGUN_API_KEYMailgun API keykey-xxx
MAILGUN_DOMAINMailgun sending domainmg.example.com
MAILGUN_REGIONMailgun region (us or eu)us

See Driver Configuration for all available providers.

Storage (Optional)

VariablePurposeExample
STORAGE_DRIVERStorage provider package nametoast-driver-storage-s3
S3_ENDPOINTS3-compatible endpointhttps://s3.amazonaws.com
S3_ACCESS_KEY_IDAccess keyAKIA...
S3_SECRET_ACCESS_KEYSecret keywJal...
S3_BUCKETBucket nametoast-uploads
S3_REGIONRegionus-east-1
S3_PUBLIC_URLPublic URL for uploaded fileshttps://cdn.example.com

TipTap Cloud (Optional)

Real-time collaboration and AI features require a TipTap Cloud account.

VariablePurpose
VITE_TIPTAP_COLLAB_APP_IDCollaboration app ID (client)
TIPTAP_COLLAB_SECRETCollaboration JWT secret (server)
VITE_TIPTAP_AI_APP_IDAI app ID (client)
TIPTAP_AI_SECRETAI JWT secret (server)

Admin Panel (Build Time)

VariablePurposeDefault
VITE_API_URLAPI endpoint baked into the JS bundle(empty/relative)

Changing VITE_API_URL requires rebuilding the admin image. A restart won't pick up the change because it's embedded in the static JavaScript.

Production Checklist

Before going live, verify:

  • BETTER_AUTH_SECRET is a unique, random 32+ character string
  • EMAIL_DRIVER is set to a real provider (not console)
  • EMAIL_FROM is set to a verified sender address
  • ADMIN_URL matches the actual admin panel domain
  • BETTER_AUTH_URL matches the actual API domain
  • Database has SSL enabled for the connection
  • TipTap tokens are set if you want collaboration/AI features
  • Storage driver is configured if you want image uploads

On this page