Orientation

Running it locally

From a fresh clone to pushing your first repository, and what each process is for.

You need Bun and Docker.

Start the infrastructure

docker compose up -d

compose.yaml starts Postgres, RustFS (an S3-compatible store; console on http://localhost:9001), a one-shot container that creates the ghost bucket, and lgtm (Grafana with OpenTelemetry, see Operations).

Install and configure

bun install
cp .env.example .env

Set BETTER_AUTH_SECRET to any long random string. The rest of .env.example already matches compose. Turborepo loads the root .env for every app, and the API's ConfigModule reads it too.

Create the tables

bun run db:migrate

This runs drizzle-kit migrate in packages/db against DATABASE_URL. It creates tables only. Once the API has built, seed gh as a built-in OAuth app if you want gh auth login --web:

node apps/api/dist/scripts/seed-github-cli-app.js

Run everything

bun run dev
ProcessURL
Webhttp://localhost:3000
API (REST under /api, git at the root)http://localhost:3001
Docshttp://localhost:3003
Coursehttp://localhost:3005

Push something

Sign up in the web app, create a repository, then create an API key under account settings. The key is the git password; your username is the username.

git remote add origin http://localhost:3001/<username>/<repo>.git
git push -u origin main

Open the bucket in the RustFS console: you will find repos/<repositoryId>/index and one object under repos/<repositoryId>/entries/ per push. That is the repository. The bare repository under the API's temp directory is only a cache.

SSH, optionally

ssh-keygen -t ed25519 -N '' -f ghost_host_key
echo "GIT_SSH_HOST_KEY=$(base64 -i ghost_host_key)" >> .env
git remote set-url origin ssh://git@localhost:1031/<username>/<repo>.git

Add your public key under Settings → Security. Without GIT_SSH_HOST_KEY the SSH listener never starts; that variable doubles as the feature flag.

Optional pieces

Each is off until configured, and Ghost runs without it:

FeatureNeeds
Code searchZOEKT_URL and the zoekt service
EmailMAIL_* or EMAIL_PROXY, and apps/delivery running
WebhooksWEBHOOK_SECRET_KEY and apps/delivery
GitHub importIMPORTER_URL, IMPORTER_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET and apps/importer
Signed web commitsCOMMIT_SIGNING_KEY

Your dev database is not a test database

Never point TEST_DATABASE_URL at a database you care about. Some integration specs drain the whole event outbox and run migrations. Use a throwaway container; the next lesson shows how.

Try it

Push a repository, then stop the API, delete its cache directory ($TMPDIR/ghost/<repositoryId>.git, see apps/api/src/services/git/repository-storage/repository-storage.service.ts › CACHE_ROOT), start it again and clone. The clone works: the API replayed the log from the bucket. You just exercised ADR 0004 and ADR 0012.

On this page