Docker Compose
Deploy Broch on any Linux host with Docker: a cloud VM, an on-premises server or bare metal. It’s the most portable option.
The compose files live in broch-io/broch-deploy under docker-compose/. This page covers the decisions before you clone; each variant’s README has its setup steps.
Prerequisites
Section titled “Prerequisites”- Docker Engine 24+ with Compose V2
- Wildcard domain & DNS configured
- TLS certificate approach decided: Caddy automatic or your own certificate
- An email address for your Let’s Encrypt account (
CADDY_ACME_EMAIL), for the Caddy automatic variants (with-postgres,with-postgres-external). Compose won’t start while it’s blank - Identity provider app registration created
Pick a Variant
Section titled “Pick a Variant”Three compose variants differ in where the database lives and how the certificate is issued:
| Variant | Database | TLS | Use when |
|---|---|---|---|
with-postgres |
Bundled Postgres | Caddy automatic, ACME DNS-01 | A single public VM, with DNS on a Caddy-supported provider (Cloudflare, Route 53 and others). |
with-postgres-external |
External (managed or self-run) | Caddy automatic | You need encryption at rest or point-in-time recovery, or already run Postgres. |
with-postgres-byo-cert |
Bundled Postgres | Your own certificate files | DNS isn’t on a Caddy-supported provider, you use your own PKI or a commercial CA, or egress is restricted. |
Each variant’s README walks the steps. The flow is the same:
git clone https://github.com/broch-io/broch-deploy.gitcd broch-deploy/docker-compose/<your-variant>
cp .env.example .env$EDITOR .env # hostname, identity provider, secrets, Let's Encrypt email, DNS token
docker compose up -dcurl https://broch.{your-domain}/healthzEach variant’s compose file maps the .env values, such as BROCH_WILDCARD_HOSTNAME and POSTGRES_PASSWORD, to the startup settings Broch reads, and passes Broch only the variables it lists. Environment Variables explains how to set any other.
Compose refuses to start, naming the variable, while any of these is blank: BROCH_MASTER_KEY, BROCH_WILDCARD_HOSTNAME, CADDY_ACME_EMAIL (automatic certificates), and POSTGRES_PASSWORD (bundled database) or BROCH_DB_CONNECTION_STRING (external). Wrap a value containing spaces, # or $ in single quotes, such as AUTHENTICATION__CLIENTSECRET='p#ss$word'; a single-quoted value can’t contain ' or end with \.
/healthz returns 200 once the process is running. Then sign in and finish setup: see After You Install.
Upgrading
Section titled “Upgrading”Back up first, then, in your variant’s directory:
$EDITOR .env # set BROCH_VERSION to the new releasedocker compose pull brochdocker compose up -dDatabase migrations run on startup, and active tunnels reconnect on their own. Upgrading & Rollback covers checking the result and rolling back.
Firewall Requirements
Section titled “Firewall Requirements”| Port | Required for |
|---|---|
| 80 | HTTP → HTTPS redirect |
| 443 | HTTPS API, tunnel proxy, WebSocket transport (TCP, plus UDP for HTTP/3) |
Backup
Section titled “Backup”For bundled Postgres:
docker compose exec -T postgres pg_dump -U broch brochdb > backup-$(date +%Y%m%d).sqlSchedule this daily and store it off-site, and take one before every upgrade. Keep a copy of .env too: it holds the master key and the database password. With with-postgres-external, use your provider’s snapshots instead.
To restore, for example to roll back an upgrade:
docker compose stop brochdocker compose exec -T postgres dropdb -U broch brochdbdocker compose exec -T postgres createdb -U broch -O broch brochdbdocker compose exec -T postgres psql -v ON_ERROR_STOP=1 -U broch -d brochdb < backup-<date>.sql$EDITOR .env # set BROCH_VERSION back to the backup's versiondocker compose up -dThese commands use the default POSTGRES_USER (broch) and POSTGRES_DB (brochdb); use yours if you changed them. On a new host, restore .env first and start the database with docker compose up -d --wait postgres, which returns once it’s ready.
Capacity
Section titled “Capacity”From load testing on 0.5 vCPU / 1 GB RAM:
| Users | Minimum vCPU | Minimum RAM |
|---|---|---|
| Up to 100 | 0.5 | 1 GB |
| 100–300 | 1 | 2 GB |
| 300+ | 2 | 4 GB |
Beyond one instance, see Scaling.
Troubleshooting
Section titled “Troubleshooting”Health Check Fails
Section titled “Health Check Fails”- Connection refused, or the
brochcontainer is unhealthy: Broch refused to start.docker compose psshows its state, anddocker compose logs brochnames the setting it refused, such as a master key under 32 bytes or a missing Auth0 audience.docker compose run --rm --no-deps broch --check-configlists every configuration problem in the environment at once. /healthz/readyreturns 503: there’s no valid license yet, which is expected until setup finishes, or the license was rejected or can’t be activated. Sign in and finish setup, or look for the license error indocker compose logs broch.
Tunnels Won’t Connect
Section titled “Tunnels Won’t Connect”Check that the firewall allows inbound 80 and 443, and that the wildcard DNS record resolves.
Database Connection Fails (Bundled)
Section titled “Database Connection Fails (Bundled)”POSTGRES_PASSWORD can’t contain ; or ", or start or end with a space. Changing it after the first docker compose up doesn’t change the database’s password: Postgres keeps the one it started with. Set it back, or change it in Postgres: docker compose exec postgres psql -U broch -d brochdb -c "ALTER ROLE broch PASSWORD '<new-password>'".
Database Connection Fails (External)
Section titled “Database Connection Fails (External)”Check BROCH_DB_CONNECTION_STRING: the host name, the database name, firewall rules and the TLS mode. Use SSL Mode=VerifyFull with the server’s real host name. Broch logs the underlying Npgsql error: docker compose logs broch | grep -i postgres.