AWS ECS Fargate (Beta)
Deploy Broch on AWS with ECS Fargate, an Application Load Balancer, and RDS PostgreSQL. The Terraform module lives in broch-io/broch-deploy/terraform/aws-ecs — clone the repo, fill in terraform.tfvars, and terraform apply.
Beta. This module is less exercised by automation than the AWS appliance. Test it before you rely on it.
What Gets Provisioned
Section titled “What Gets Provisioned”- VPC with 2 public + 2 private subnets across 2 AZs, single NAT gateway
- Application Load Balancer with ACM cert (hostname + wildcard, DNS-validated via Route 53)
- ECS Fargate cluster + task definition + service, one task (no autoscaling)
- RDS PostgreSQL 17 in private subnets, encrypted at rest (db.t4g.micro by default)
- Secrets Manager entries: master key (generated
BROCH_MASTER_KEY), IdP client secret, DB password, connection string - IAM execution role scoped tight to just this stack’s secrets
- Route 53 A-ALIAS records: hostname + wildcard pointing at the ALB
- CloudWatch Logs group, 30-day retention
All resources are created in your account. Broch has no access to your environment.
This stack runs ALB + Fargate + RDS + a NAT gateway. To estimate spend, use the AWS Pricing Calculator with your chosen sizes; the module README notes the resource tradeoffs.
Prerequisites
Section titled “Prerequisites”- Terraform 1.6+
- AWS credentials with permissions to create VPC, ECS, RDS, ALB, Route 53, IAM, ACM, and Secrets Manager resources
- A Route 53 hosted zone for your wildcard hostname’s parent domain
- Your hostname chosen; the module creates its records in the Route 53 zone
- Identity provider app registration created
- License — activated in-app after first sign-in (nothing to set before deploy)
git clone https://github.com/broch-io/broch-deploy.gitcd broch-deploy/terraform/aws-ecs
cp terraform.tfvars.example terraform.tfvars$EDITOR terraform.tfvars # non-secret values; delete the auth_client_secret line
# Pass the IdP client secret as an environment variable, not in the file.# Set it before every plan, apply or destroy (upgrades included): an empty# value overwrites the stored secret. Paste the value, then press Enter.read -rs TF_VAR_auth_client_secretexport TF_VAR_auth_client_secret
terraform initterraform planterraform apply # 5-10 min on first run; RDS is the long poleterraform plan refuses settings Broch would refuse at startup: an unknown auth_provider, a missing client ID or secret, a blank wildcard_hostname, or a value your provider needs (Auth0: auth_domain and auth_audience; Okta: auth_domain; Entra ID: auth_tenant_id and auth_instance, such as https://login.microsoftonline.com/; generic OIDC: auth_authority).
After apply:
echo "Broch is at: $(terraform output -raw broch_url)"DNS records are created automatically by the Terraform — no manual record entry needed. The ACM cert is DNS-validated against your Route 53 zone, also automatic.
The Terraform state holds every secret in plaintext (master key, client secret, database credentials): keep terraform.tfstate somewhere access-controlled, such as an S3 remote backend.
Verify
Section titled “Verify”curl "$(terraform output -raw broch_url)/healthz" # liveness — 200 once the process is upcurl "$(terraform output -raw broch_url)/healthz/ready" # readiness — 200 once the license and database check out/healthz/ready returns 503 until an admin completes setup. Sign in at your Broch URL and finish in the browser: see Self-Managed Deployment → After You Install.
Upgrading
Section titled “Upgrading”Update the image tag and re-apply, then point the service at the new task definition. apply registers a new task-definition revision but deliberately leaves the service on the old one, so apply alone does not upgrade. The aws commands on this page assume the default name_prefix (broch) and that your CLI region matches aws_region (add --region otherwise):
$EDITOR terraform.tfvars # set broch_image = "ghcr.io/broch-io/broch:<new-version>"read -rs TF_VAR_auth_client_secret # the current value, unless you're rotating itexport TF_VAR_auth_client_secretterraform apply
# Naming the family (not a revision) deploys its latest revision.aws ecs update-service \ --cluster broch-cluster \ --service broch-service \ --task-definition broch-brochECS starts the new task, then stops the old one; tunnels reconnect on their own. Database migrations run automatically on the new task’s first start. Any other task-definition change, such as task_cpu, task_memory or an auth_* setting, needs the same update-service step after apply.
For force-refreshing the image at the same tag (e.g. pulling a re-tagged :latest):
aws ecs update-service \ --cluster broch-cluster \ --service broch-service \ --force-new-deploymentBackup
Section titled “Backup”RDS handles automated backups with the configured retention period (7 days default — see backup_retention_period in the module’s database.tf). Snapshot manually before risky operations:
aws rds create-db-snapshot \ --db-instance-identifier broch-postgres \ --db-snapshot-identifier broch-pre-upgrade-$(date +%Y%m%d)Rotating Secrets
Section titled “Rotating Secrets”Terraform owns the Secrets Manager entries, so rotate through it: a direct edit bypasses Terraform’s state, and the next change to the variable overwrites it. To rotate the IdP client secret, set the new value and apply, then force a new ECS deployment so the task reads it:
read -rs TF_VAR_auth_client_secret # paste the new value, then Enterexport TF_VAR_auth_client_secretterraform apply
aws ecs update-service \ --cluster broch-cluster \ --service broch-service \ --force-new-deploymentTradeoffs: What the Default Module Is Not
Section titled “Tradeoffs: What the Default Module Is Not”The default terraform.tfvars.example values prioritise a minimal footprint over availability. Before production:
- Set
multi_az = trueon the RDS resource for cross-AZ failover - Set
skip_final_snapshot = false(with afinal_snapshot_identifier) on RDS. Deletion protection is already on (rds_deletion_protection) - Database TLS isn’t authenticated: the connection string sets no
SSL Mode, so the RDS certificate isn’t verified. See the module README - Add WAF in front of the ALB if you’re exposed to abuse
See the module README for the full list.
Teardown
Section titled “Teardown”RDS deletion protection is on by default, so turn it off first, with TF_VAR_auth_client_secret still set:
terraform apply -var rds_deletion_protection=falseterraform destroy -var rds_deletion_protection=falseskip_final_snapshot = true in the default config means the Postgres data is deleted permanently. Snapshot first if you want to keep it.