Back to Ghost

Development setup

docs/contributing/development-setup.md

6.62.09.4 KB
Original Source

Development setup

This guide runs the Ghost monorepo in its standard development configuration: Ghost Core and its backing services run in Docker, while frontend build watchers run on the host.

Prerequisites

Install:

For Node.js support in older Ghost releases, see the Node.js compatibility reference.

The default environment binds ports 80, 2368, 3306, 6379, 8025, and 8026. Stop local services using those ports before starting Ghost.

The repository pins its pnpm version in package.json. Activate that version before first use rather than installing a separate global version of pnpm:

bash
corepack enable pnpm

Clone the repository

Clone the canonical repository with its submodules:

bash
git clone --recurse-submodules [email protected]:TryGhost/Ghost.git
cd Ghost

If you already cloned without submodules, the setup command in the next section initializes them. Contributors without write access can create a fork and add it as a remote when they are ready to submit a pull request; a fork is not required to run Ghost locally.

Install the workspace

From the repository root:

bash
pnpm setup

pnpm setup installs the workspace and initializes all Git submodules. Run it after a fresh clone and whenever a branch changes workspace dependencies or submodules.

Start Ghost

bash
pnpm dev

The first run builds the development image and may take longer than subsequent starts. The command starts:

  • Ghost Core, MySQL, Redis, and Mailpit in Docker
  • a Caddy gateway in Docker on http://localhost:2368
  • Admin and Portal development watchers on the host

Wait for Docker Compose to report healthy services, then open:

On a new database, the Admin URL opens Ghost's setup screen. Create a local owner account there; the development environment does not define shared login credentials.

As a quick health check, confirm that the site and Admin load and that docker compose -f compose.dev.yaml ps reports the Docker services as running or healthy.

Press Ctrl+C in the development process to stop its watchers and containers. Docker volumes preserve the database and uploaded development content between runs.

Accessing services

ServiceAddress
Ghost sitehttp://localhost:2368
Ghost site (gateway alias)http://localhost
Ghost Adminhttp://localhost:2368/ghost/
Mailpithttp://localhost:8025
Mailpit (E2E)http://localhost:8026
MySQLlocalhost:3306 using the ghost_dev database
Redislocalhost:6379
Tinybirdhttp://localhost:7181 with pnpm dev:analytics
MinIO consolehttp://localhost:9001 with pnpm dev:storage
MinIO S3 APIhttp://localhost:9000 with pnpm dev:storage

Development variants

Run one root command at a time. Each variant includes the standard development environment and adds the listed tooling:

CommandUse it when working on
pnpm devGhost Core, Admin, or Portal
pnpm dev:publicComments UI, Signup Form, Search, Announcement Bar, or Admin Toolbar
pnpm dev:lexicalKoenig's Lexical editor inside Ghost Admin
pnpm dev:analyticsTinybird-backed analytics with the latest published version of the Traffic Analytics service
pnpm dev:analytics:localTinybird-backed analytics with your locally running instance of the Traffic Analytics service
pnpm dev:storageS3-compatible storage through MinIO on ports 9000 and 9001
pnpm dev:stripeStripe webhooks exactly as production receives them; requires Tailscale, see below
pnpm dev:fullPublic app watchers plus analytics, storage, and Stripe

Copy .env.example to .env only when you need an optional integration. Never commit credentials or the local .env file.

To open Ghost on a phone or another computer, or to exercise HTTPS, subdirectory, and separate-Admin URL behaviour, see Testing development URLs and devices.

Stripe webhooks

pnpm dev:stripe runs the webhook path production runs. It publishes Ghost's webhook route, and nothing else, through Tailscale Funnel, and Ghost registers a pinned webhook endpoint at that address once Stripe is connected in Admin, then deletes it on shutdown. The site and Admin stay on localhost, so hot reload and the rest of the development environment work as usual. Use it when the shape of a webhook payload matters, for example when reading new fields from a checkout session. Ghost logs an error whenever an event arrives rendered at a different API version from the one it pins, in any environment.

The webhook route is reachable from the internet while the command runs; every request to it must carry a valid Stripe signature. The tunnel is a child process of the command and ends with it, including on Ctrl-C. Only a forced kill of the command can leave the tunnel running, and even then it does not survive a restart of Tailscale or the machine.

Funnel needs Tailscale 1.52 or newer with MagicDNS, HTTPS certificates and Funnel enabled for your tailnet and node. The command reports when Tailscale is missing, not signed in, or has no MagicDNS name; for the other requirements it shows Tailscale's own error.

pnpm dev:stripe --listen forwards events with stripe listen instead, which needs STRIPE_SECRET_KEY in the environment or a local .env file but no Tailscale. The CLI renders every event at your Stripe account's default API version, which cannot be pinned, so an event can carry a different shape from the one production receives; the command warns about this at startup and Ghost logs an error when a mismatched event arrives. Use it only when the payload shape does not matter.

Data and email

After creating the local owner account, populate a development site with stable sample data:

bash
pnpm reset:data

This clears the development database while preserving the owner, then creates 1,000 members and 100 posts. Use pnpm reset:data:empty for an empty site. Both commands are destructive and require the Docker development environment to be running.

When developing a database migration, apply pending migrations to the running development database with:

bash
pnpm migrate:db

Development email is captured by Mailpit rather than delivered. Open http://localhost:8025 to inspect messages.

Updating and recovering

Before starting new work, update your local main from the canonical repository:

bash
git fetch origin
git switch main
git pull --ff-only origin main
pnpm setup

If dependencies or Nx state become inconsistent after switching branches, run:

bash
pnpm fix

This prunes the pnpm store, removes workspace node_modules directories, reinstalls dependencies, and resets Nx state.

For narrower build and cache problems, use:

bash
pnpm nx reset       # Clear the Nx cache
pnpm build:clean    # Clear the Nx cache and Ghost build output
pnpm docker:build   # Rebuild the local development images

To stop containers outside a running pnpm dev process:

bash
pnpm docker:down

As a last resort, pnpm docker:clean removes the development containers, volumes, and locally built images. This deletes the local development database and uploaded content; do not use it when you need to preserve that data.

If startup fails, inspect docker compose -f compose.dev.yaml ps and docker compose -f compose.dev.yaml logs SERVICE-NAME. Check for occupied ports, an unhealthy Docker daemon, and stale dependencies before resetting data or volumes.

Next steps

Use the README beside the area you are changing for its focused commands and architecture. The codebase documentation index links to the main workspace guides.