DigitalOcean Installation
Deploy Broch on a DigitalOcean Droplet running Docker Compose, with embedded PostgreSQL on attached block storage. Caddy handles automatic wildcard TLS via ACME DNS-01.
The Terraform module lives in broch-io/broch-deploy/terraform/digitalocean — clone the repo, fill in terraform.tfvars, terraform apply, then point your wildcard DNS at the reserved IP.
What gets provisioned
Section titled “What gets provisioned”- DigitalOcean Droplet (Ubuntu 24.04,
s-1vcpu-1gbdefault) - Block storage volume (10 GB default) for persistent Postgres data
- Reserved IP for stable DNS target
- Firewall: only SSH (22) and HTTPS (443) inbound
- cloud-init bootstrap: Docker Compose + Caddy (auto-TLS) + broch + postgres:16-alpine
BROCH_MASTER_KEY(the at-rest encryption root) generated on first boot into the Droplet’s.env— never typed, never leaves the Droplet
This is the simplest cloud module — a single Droplet. The tradeoff is the obvious one: single VM, no managed services, no failover. To estimate spend, use DigitalOcean’s pricing for your chosen Droplet size.
Prerequisites
Section titled “Prerequisites”- Terraform 1.6+
- DigitalOcean account with an API token
- SSH key registered in DigitalOcean — note the fingerprint
- A registered domain on a Caddy-supported DNS provider (Cloudflare, GoDaddy, Route 53)
- DNS provider API token with permission to edit the zone hosting your wildcard hostname
- Wildcard domain & DNS configured (point at the reserved IP after first apply)
- Identity provider app registration created
- License — activated in-app after first sign-in (nothing to set before deploy)
This deployment uses Caddy with ACME DNS-01 for automatic wildcard TLS — no manual certificate management. You provide your DNS provider’s API token at apply time; Caddy handles certificate issuance and renewal in the background.
See TLS Certificates for the broader certificate story.
git clone https://github.com/broch-io/broch-deploy.gitcd broch-deploy/terraform/digitalocean
cp terraform.tfvars.example terraform.tfvars$EDITOR terraform.tfvars
terraform initterraform apply # 3-4 min — Droplet + cloud-init + image pullAfter apply:
echo "Reserved IP: $(terraform output -raw droplet_ip)"
# Create a wildcard A record:# *.tunnels.company.com → <reserved-ip>
# Wait for Caddy to issue certs (~30-60s once DNS propagates), then verify:curl -fsS "$(terraform output -raw broch_url)/healthz"SSH access
Section titled “SSH access”$(terraform output -raw ssh_command)# → ssh root@<reserved-ip>The Droplet runs Docker Compose at /opt/broch/. docker compose ps shows the running services; docker compose logs broch-server tails server output.
Upgrading
Section titled “Upgrading”Update image_tag in terraform.tfvars and re-apply. This recreates the Droplet (cloud-init re-runs with the new tag). Postgres data is preserved on the attached block storage volume, which is detached/reattached during the cycle. Expect ~3–4 min of downtime.
For zero-downtime upgrades, the AWS ECS or Azure Container Apps modules use rolling deploys via their respective container schedulers.
Backup
Section titled “Backup”# Snapshot Postgres to your local machinessh root@$(terraform output -raw droplet_ip) \ "cd /opt/broch && docker compose exec -T postgres pg_dump -U broch brochdb" \ > broch-$(date +%Y%m%d).sql
# Restorecat broch-20260525.sql | ssh root@$(terraform output -raw droplet_ip) \ "cd /opt/broch && docker compose exec -T postgres psql -U broch brochdb"Block storage volume snapshots via the DigitalOcean console are also fine and cover everything (broch state + Caddy cert cache + Postgres data).
Tradeoffs / what the default module is not
Section titled “Tradeoffs / what the default module is not”- Single Droplet — the simplest cloud Broch. When you need HA or scale beyond a single VM, move to AWS ECS or Azure Container Apps.
- Embedded Postgres — one less service to operate. When you need encryption-at-rest, PITR, or multi-replica, use DO Managed Databases instead and switch to the docker-compose
with-postgres-externalvariant on the same Droplet. s-1vcpu-1gbdefault — a minimal size for evaluation. Bumpdroplet_sizefor real load.- Single AZ — DO Droplets are AZ-bound. Cross-AZ HA needs a load balancer + multi-droplet setup, not in this module.
- Firewall: SSH from
0.0.0.0/0— convenient for setup; lock to your bastion / VPN CIDR before going to real prod. - No automated DB backups — configure your own via cron + DO Spaces or external storage.
See the module README for the full list.
Teardown
Section titled “Teardown”terraform destroyThe block storage volume is destroyed along with the rest — if you want to keep Postgres data, snapshot the volume or export the database before running destroy.