Skip to content

Azure Installation

Deploy Broch on Azure with Container Apps, Postgres Flexible Server, and Key Vault for secrets. The Terraform module lives in broch-io/broch-deploy/terraform/azure-container-apps — clone the repo, fill in terraform.tfvars, terraform apply, then bind the custom domain.

Marketplace listing: an Azure Marketplace listing with a Bicep template is on the roadmap. Until then, the Terraform module is the supported deployment path.

  • Resource group + Log Analytics workspace (required by Container Apps Environment)
  • Container Apps Environment + Container App with HTTPS ingress
  • User-assigned managed identity for the Container App
  • Key Vault (RBAC mode) holding the generated BROCH_MASTER_KEY, the OIDC client secret, and the Postgres connection string
  • Postgres Flexible Server v16 + database, with the AllowAzureServices firewall rule
  • Custom domain stub on the Container App (binding requires a one-time manual step — see below)

All resources are created in your subscription. Broch has no access to your environment.

This stack runs Container Apps + Postgres Flexible Server, with no equivalent of an ALB or NAT gateway — a simpler footprint than the AWS stack. To estimate spend, use the Azure Pricing Calculator with your chosen sizes.

  • Terraform 1.6+ + Azure CLI (az login against the target subscription)
  • Owner or Contributor + User Access Administrator on the target subscription (Key Vault RBAC role assignment needs the latter)
  • DNS control for your wildcard hostname’s parent domain (Azure DNS or any provider — you create records yourself)
  • Wildcard domain & DNS configured
  • Identity provider app registration created
  • License — activated in-app after first sign-in (nothing to set before deploy)
Terminal window
az login
az account set --subscription <subscription-id>
git clone https://github.com/broch-io/broch-deploy.git
cd broch-deploy/terraform/azure-container-apps
cp terraform.tfvars.example terraform.tfvars
$EDITOR terraform.tfvars
terraform init
terraform plan
terraform apply

This gets the Container App running on its default *.azurecontainerapps.io hostname. The custom-domain binding is a separate one-time manual step because Azure needs you to prove DNS control before it’ll bind the cert.

Bind the custom domain (one-time, post-apply)

Section titled “Bind the custom domain (one-time, post-apply)”
Terminal window
VERIF_ID=$(terraform output -raw container_app_verification_id)
HOSTNAME=$(terraform output -raw broch_url | sed 's|https://||')
# Add these DNS records in your provider:
# A <hostname> → IP of the Container App
# TXT asuid.<hostname> → $VERIF_ID
# Azure requires the TXT to validate ownership.
az containerapp hostname bind \
--hostname "$HOSTNAME" \
--resource-group broch-rg \
--name broch-app \
--validation-method CNAME

Re-run as needed if cert provisioning hasn’t propagated (~5–10 min).

Azure Container Apps’ managed certs don’t issue wildcards. For tunnel subdomains (*.tunnels.example.com), you have three options:

  1. Front Door / Application Gateway with a managed wildcard in front of Container Apps. Adds a Front Door resource but is the cleanest production answer.
  2. Provision a wildcard cert separately (Let’s Encrypt via certbot + DNS-01, or a commercial CA) and upload it via az containerapp env certificate upload + az containerapp hostname bind.
  3. Skip wildcards if your deployment doesn’t use tunnel subdomains.

The module README covers the binding flow in more detail.

Terminal window
$EDITOR terraform.tfvars # set broch_image = "ghcr.io/broch-io/broch:1.6.0"
terraform apply

Container Apps rolls out a new revision with the new image. Old revision drains based on the ingress traffic-weight config (100% to latest by default).

Database migrations run automatically on the new revision’s first start.

Edit the Key Vault secret value, then restart the Container App revision:

Terminal window
az keyvault secret set \
--vault-name $(terraform output -raw key_vault_name) \
--name auth-client-secret \
--value "<new-client-secret>"
az containerapp revision restart \
--name broch-app \
--resource-group broch-rg \
--revision $(az containerapp revision list --name broch-app --resource-group broch-rg --query '[0].name' -o tsv)

Postgres Flexible Server performs automated daily backups with point-in-time restore; retention is fixed at 7 days. Restore through the Azure portal or az postgres flexible-server restore. Take a manual backup before risky operations — see the Azure Database for PostgreSQL backup docs.

Tradeoffs / what the default module is not

Section titled “Tradeoffs / what the default module is not”
  • Public-access Postgres with the AllowAzureServices firewall rule. For VNet-private Postgres, add delegated_subnet_id + private_dns_zone_id to the postgres resource and drop the firewall rule.
  • Single replica (min_replicas = max_replicas = 1). No autoscaling configured — broch’s workload is usually steady-state and predictable bill is preferable.
  • No Front Door in front of Container Apps. Add it when you want wildcard certs managed automatically, or for global edge caching.
  • purge_protection_enabled = false on Key Vault for fast iteration. Flip to true before going to real production.

See the module README for the full list.

If you’d rather run on an Azure VM with Docker Compose (simpler, single-VM), use the Docker Compose installation guide and run it on an Ubuntu VM you provision yourself.

Terminal window
terraform destroy

Key Vault soft-delete means the vault sticks around for 7 days after destroy. If you want to re-apply with the same name immediately, either change name_prefix in tfvars or purge: az keyvault purge --name <vault-name> (needs the Key Vault Contributor role).