Deployment & self-hosting
The reference topology, minimum configuration, horizontal scaling, backups, and forward-only migrations.
Reference topology#
The shipped docker-compose.yml is the reference deployment: caddy, web, api, runner, ssh-git, mcp, postgres, and redis, wired together entirely through the Docker network and the volumes listed in Architecture & deployment topology. There is no requirement to run it this way — every component is a plain container image and can be deployed to Kubernetes, ECS, or bare metal — but the compose file is the fastest path to a correct topology and is what docker compose up gives you out of the box.
Minimum required configuration#
At minimum, a self-hosted instance needs:
- A PostgreSQL connection string and a Redis URL.
- A public URL (used to build absolute links in emails, webhooks, and SAML metadata).
- JWT signing secrets (
jwt.accessSecret/jwt.refreshSecret) — long, random, and never rotated casually, since rotating them invalidates every existing session. - A secrets-encryption key (for CI/CD secret values at rest).
- The internal hook secret shared between
apps/apiand every bare repository's Git hooks.
Scaling#
apiis stateless per-request (all state lives in Postgres/Redis/volumes) and can be run as multiple replicas behind Caddy's load balancing.runneris designed to be scaled horizontally and heterogeneously — run any number of replicas, each with its ownRUNNER_NAMEandRUNNER_TAGS, to add CI capacity or dedicate hardware (GPU, ARM, high-memory) to specific job types via tag matching. See CI/CD pipelines.webis a stateless Next.js server and scales the same wayapidoes.postgresandredisare the two components that are not horizontally scaled by this architecture; standard PostgreSQL replication/Redis clustering approaches apply if needed at larger scale, transparently to the application layer.
Backups#
A consistent backup is: a pg_dump (or continuous WAL archiving) of the postgres_data volume, taken alongside a file-level snapshot of git_data, lfs_data, packages_data, oci_data, release_assets, and uploads_data. Because Postgres is authoritative for all relational state and the volumes are authoritative only for the byte content they were told to store by Postgres-recorded operations, restoring both from the same point in time (or restoring Postgres to a time at or after the volume snapshot) is sufficient for a consistent recovery — there is no separate index or cache layer that needs independent reconciliation.
Upgrades#
Database migrations (packages/database/prisma/migrations) are applied with prisma migrate deploy — additive, forward-only, and safe to run against a live database before the new application code is rolled out, since each migration is written to be backward-compatible with the previous release's queries until that release is fully retired. There is no supported downgrade path for a migration; roll forward with a corrective migration instead of attempting to reverse one against production data.
