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.
Install:
- Git
- Node.js
22.23.1(the version in.nvmrcand.node-version) - Docker with Docker Compose v2
- Corepack, included with supported Node.js distributions
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:
corepack enable pnpmClone the canonical repository with its submodules:
git clone --recurse-submodules git@github.com:TryGhost/Ghost.git
cd GhostIf 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.
From the repository root:
pnpm bootstrappnpm bootstrap installs the workspace, initializes all Git submodules and
configures Git to ignore formatting-only revisions in blame output. Run it after
a fresh clone and whenever a branch changes workspace dependencies or
submodules.
Ghost calls this command bootstrap because pnpm setup
is a pnpm CLI command for configuring pnpm's global home and updating shell
startup files. It does not run Ghost's repository initialization. Using a
distinct script name avoids silently changing a contributor's shell when the
intention is to prepare the Ghost checkout.
pnpm devThe 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:
- Site: http://localhost:2368
- Admin: http://localhost:2368/ghost/
- Development email: http://localhost:8025
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.
| Service | Address |
|---|---|
| Ghost site | http://localhost:2368 |
| Ghost site (gateway alias) | http://localhost |
| Ghost Admin | http://localhost:2368/ghost/ |
| Mailpit | http://localhost:8025 |
| Mailpit (E2E) | http://localhost:8026 |
| MySQL | localhost:3306 using the ghost_dev database |
| Redis | localhost:6379 |
| Tinybird | http://localhost:7181 with pnpm dev:analytics |
| VersityGW WebUI | http://localhost:9001 with pnpm dev:storage |
| VersityGW S3 API | http://localhost:9000 with pnpm dev:storage |
Sign in to the VersityGW WebUI with access key s3-user and secret key
s3-pass.
Run one root command at a time. Each variant includes the standard development environment and adds the listed tooling:
| Command | Use it when working on |
|---|---|
pnpm dev |
Ghost Core, Admin, or Portal |
pnpm dev:public |
Comments UI, Signup Form, Search, Announcement Bar, or Admin Toolbar |
pnpm dev:lexical |
Koenig's Lexical editor inside Ghost Admin |
pnpm dev:analytics |
Tinybird-backed analytics with the latest published version of the Traffic Analytics service |
pnpm dev:analytics:local |
Tinybird-backed analytics with your locally running instance of the Traffic Analytics service |
pnpm dev:storage |
S3-compatible storage through VersityGW, with its WebUI on port 9001 |
pnpm dev:stripe |
Stripe webhooks exactly as production receives them; see Stripe testing |
pnpm dev:mailgun |
Mailgun API delivery; see email testing |
pnpm dev:fake-mailgun |
Capture newsletters and bulk email in Mailpit through a fake Mailgun API; see email testing |
pnpm dev:full |
Public 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.
After creating the local owner account, populate a development site with stable sample data:
pnpm reset:dataThis 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. See Working with test data for larger and custom
datasets.
When developing a database migration, apply pending migrations to the running development database with:
pnpm migrate:dbDevelopment email is captured by Mailpit rather than delivered. Open http://localhost:8025 to inspect messages. For Mailgun delivery and automated-test workflows, see Email testing.
Before starting new work, update your local main from the canonical repository:
git fetch origin
git switch main
git pull --ff-only origin main
pnpm bootstrapIf dependencies or Nx state become inconsistent after switching branches, run:
pnpm fixThis prunes the pnpm store, removes workspace node_modules directories,
reinstalls dependencies, and resets Nx state.
For narrower build and cache problems, use:
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 imagesTo stop containers outside a running pnpm dev process:
pnpm docker:downAs 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.
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.