Toast
Contributor

Local Dev Setup

Get your development environment running from scratch.

This guide walks you through setting up a local Toast development environment. For the short version, see the CONTRIBUTING.md checklist.

Prerequisites

  • Node.js 24+ — check with node --version
  • pnpm — installed via corepack (corepack enable)
  • Docker — required for PostgreSQL and VersityGW (object storage)
  • Git — for cloning and hooks

Quick Start

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

pnpm dx is the single command that handles everything: starts Docker containers, runs database migrations, seeds data, and launches all dev servers.

What pnpm dx Does

On first run, pnpm dx detects it's a fresh checkout and runs the bootstrap sequence:

  1. Port detection — checks if default ports (5432, 9000, 3000, 5173) are available
  2. Environment setup — creates .env from .env.example, prompting for custom ports if needed
  3. Docker Compose — starts PostgreSQL 16 and VersityGW (S3-compatible object storage)
  4. Database migrations — applies all Drizzle ORM migrations
  5. Database seeding — creates a default site and admin user
  6. Dev servers — starts the API server (port 3000) and admin panel (port 5173) via Turborepo

On subsequent runs, it skips the bootstrap and goes straight to starting Docker and dev servers.

Non-Interactive Mode

For CI or scripting, skip all prompts:

DX_AUTO=1 pnpm dx

Or manually create .env before running:

cp .env.example .env
# Edit ports if needed
pnpm dx

TipTap Pro Extensions (optional)

pnpm install needs no token or registry setup — the repo has no private dependencies. The editor's Pro extensions (AI, comments, and the collaboration provider) are a paid, optional add-on and are not installed by default. The editor degrades gracefully without them via apps/admin/src/lib/tiptap-pro-adapter.ts.

To enable them you need a paid TipTap Cloud subscription. Set the registry token and run the opt-in installer:

export TIPTAP_PRO_TOKEN=<token-from-tiptap-cloud>
./scripts/enable-tiptap-pro.sh

This installs the @tiptap-pro/* packages into your checkout as a local overlay. It does not add them to any package.json or the committed lockfile, so the repo stays reproducible and token-free — do not commit the resulting apps/admin/package.json / pnpm-lock.yaml changes. The script writes registry auth to your user-level ~/.npmrc (pnpm 10 does not expand env vars in the project .npmrc).

TipTap Runtime Features

TipTap has a two-layer model:

  • TIPTAP_PRO_TOKEN — private-registry auth used only by ./scripts/enable-tiptap-pro.sh to install the optional Pro extensions; not needed for a normal install or build
  • Runtime capabilities — controlled by env pairs:
    • Collaboration: VITE_TIPTAP_COLLAB_APP_ID + TIPTAP_COLLAB_SECRET
    • AI: VITE_TIPTAP_AI_APP_ID + TIPTAP_AI_SECRET

The admin panel checks /api/capabilities at runtime to gate features — it never reads env vars directly.

Verify Setup

After pnpm dx completes, verify everything is running:

# API health check
curl http://localhost:3000/healthz        # {"status":"ok"}
curl http://localhost:3000/healthz/ready  # {"status":"ok","database":"connected"}

The admin panel runs at http://localhost:5173. Default login:

  • Email: admin@ghost.org
  • Password: admin

Docker Services

docker-compose.dev.yml manages two services:

ServiceImageDefault PortPurpose
dbpostgres:16-alpine5432Primary database
storageghcr.io/versity/versitygw9000 / 9001 / 9002S3-compatible file storage

A storage-setup initializer creates toast-uploads and permits anonymous object downloads only. pnpm dx waits for it to succeed before starting the app. The console at http://localhost:9001 uses minioadmin / minioadmin, retained for compatibility with existing local settings. Its Admin API uses port 9002. All storage ports bind to localhost.

Set S3_PORT, S3_CONSOLE_PORT and S3_ADMIN_PORT to customize these ports, and keep S3_ENDPOINT / S3_PUBLIC_URL aligned with the API port. MINIO_PORT, MINIO_CONSOLE_PORT and the worktree --minio argument remain supported as fallbacks; new worktrees use provider-neutral names. pnpm dx derives missing console/admin ports for existing worktrees; pnpm worktree:ports:check detects conflicts.

Existing MinIO data

VersityGW starts with new storage_data and storage_iam volumes. The old minio_data volume is left untouched; it cannot be mounted directly into VersityGW. If you need its objects, keep an existing working MinIO container/image, start it separately on different ports, and copy objects through the S3 API into VersityGW. Verify the copied objects before removing any old container or volume. If the old image is unavailable, preserve its volume until a recovery path is available.

Stop the old MinIO container before starting the replacement on the same ports. Do not run docker compose down -v as a migration step: it also deletes database volumes. A fresh storage volume does not remove database records, so existing media references require an object copy or re-upload.

Run pnpm test:storage to verify the real storage contract using isolated disposable containers and volumes. CI runs this alongside database integration tests.

Port Conflicts

If port 5432 is already in use (e.g., a system PostgreSQL), you have two options:

  1. Interactive — run pnpm dx and choose custom ports when prompted
  2. Manual — edit .env to set DB_PORT=5433 and update DATABASE_URL accordingly

Git Worktrees

When working on multiple branches simultaneously, use worktrees instead of stashing:

# Create a worktree with auto-allocated ports
pnpm worktree:create my-feature-branch

# Check which ports your worktree uses
pnpm worktree:context --json

# List all worktrees and their ports
pnpm worktree:list --json

# Detect and fix port collisions
pnpm worktree:ports:check
pnpm worktree:ports:fix

Each worktree gets its own .env with unique ports so multiple branches can run dev servers simultaneously without conflicts.

Important: In a worktree, never assume default ports from docs. Always run pnpm worktree:context --json first.

Common Commands

TaskCommand
Start everythingpnpm dx
Run testspnpm test
Run a single API testpnpm --filter @toast/api test src/app.docs.test.ts
Integration testspnpm test:integration
Type checkpnpm typecheck
Lint + fixpnpm lint:fix
Format + fixpnpm format:fix
Full quality checkpnpm check
Strict check (no fix)pnpm check:strict
Generate DB migrationtd db generate <name>
Apply migrationstd db migrate
Reset databasetd db reset
Browse DB (Drizzle Kit)td db studio

Editor Setup

Install the Oxc VS Code extension for real-time lint and format feedback.

See Linting & Formatting for the full rule set.

Troubleshooting

pnpm install fails with 404 errors for @tiptap-pro/*

A plain pnpm install should never hit this — the repo has no private dependencies. If you see it, you likely have a leftover local TipTap Pro overlay (from ./scripts/enable-tiptap-pro.sh) with a missing or expired TIPTAP_PRO_TOKEN. Either set a valid token or remove the overlay (git checkout apps/admin/package.json pnpm-lock.yaml && pnpm install). See TipTap Pro Extensions above.

Docker containers won't start

Check that Docker Desktop is running and no other containers are using the same ports:

docker ps                                          # List running containers
docker compose -f docker-compose.dev.yml down      # Stop Toast containers
docker compose -f docker-compose.dev.yml up -d     # Restart

Database connection errors

Verify the database is healthy:

docker compose -f docker-compose.dev.yml ps     # Check container status
td db migrate                                   # Re-run migrations
td db reset                                     # Nuclear option: reset and re-seed

Port already in use

If a dev server fails to start because a port is taken:

lsof -i :3000               # Find what's using the port
pnpm worktree:ports:check    # Check for worktree port collisions

On this page