Skip to content

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.

  • DigitalOcean Droplet (Ubuntu 24.04, s-1vcpu-1gb default)
  • 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.

  • 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.

Terminal window
git clone https://github.com/broch-io/broch-deploy.git
cd broch-deploy/terraform/digitalocean
cp terraform.tfvars.example terraform.tfvars
$EDITOR terraform.tfvars
terraform init
terraform apply # 3-4 min — Droplet + cloud-init + image pull

After apply:

Terminal window
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"
Terminal window
$(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.

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.

Terminal window
# Snapshot Postgres to your local machine
ssh 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
# Restore
cat 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-external variant on the same Droplet.
  • s-1vcpu-1gb default — a minimal size for evaluation. Bump droplet_size for 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.

Terminal window
terraform destroy

The 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.