The infrastructure behind a 25,000-account SaaS
How BRIDGEBOOKS runs on Terraform-managed ECS Fargate: autoscaling that flexes, queues that fail loudly, and alarms that fire before customers notice.
BRIDGEBOOKS is an AI-marketing SaaS used by more than 25,000 Japanese businesses. I own its AWS infrastructure, and everything about it is written in Terraform. Here's how the pieces fit and what I'd keep doing on the next platform.
Everything in Terraform, no exceptions
The whole stack lives in Terraform: the ECS Fargate services, the Multi-AZ RDS PostgreSQL instance, Route 53, the SQS queues, every CloudWatch alarm. The rule is simple: if it isn't in the repo, it doesn't exist.
That rule pays for itself the first time you need to answer "what changed?"
during an incident. git log on the infrastructure repo is a better
incident timeline than anyone's memory. It also means staging can be a real
copy of production instead of a hopeful approximation.
Scale the workers separately from the web tier
The platform has two very different load profiles. Web traffic is steady and polite. Content publishing is bursty: a campaign kicks off and suddenly there are thousands of jobs in the queue.
So they scale independently. The app service scales on request load, and the worker service scales on queue depth. During a publishing burst the workers fan out to many times their idle count while the web tier doesn't move at all. Paying for burst capacity only when the burst exists is the whole point of Fargate.
resource "aws_appautoscaling_policy" "worker_queue_depth" {
policy_type = "TargetTrackingScaling"
target_tracking_scaling_policy_configuration {
customized_metric_specification {
metric_name = "ApproximateNumberOfMessagesVisible"
namespace = "AWS/SQS"
statistic = "Average"
}
target_value = 100 # jobs per worker before we add another
}
}Dead-letter queues are the honest failure mode
Every SQS queue has a dead-letter queue, and the DLQs have alarms on them. A job that fails repeatedly doesn't vanish or retry forever. It lands somewhere visible, with its payload intact, waiting to be inspected and replayed.
This is the difference between "a customer emailed us about missing content" and "we saw three publish jobs dead-letter overnight and replayed them before Japan woke up." With clients in a timezone one hour ahead of mine, that second version matters.
Alarm on symptoms, not vibes
The platform runs a small set of CloudWatch alarms, and each one maps to a question a human would actually ask: is the queue backing up, is the database CPU pinned, is ECS starved, is the load balancer returning errors.
The discipline is keeping the set small. Every alarm pages someone, so every alarm has to be worth interrupting someone's evening for. An alarm nobody acts on trains the team to ignore all of them.
What I'd tell past me
Start with Terraform even when the stack feels too small to deserve it. Split worker scaling from web scaling on day one. Put a DLQ behind every queue before the first job fails, not after. None of this is clever, which is exactly why it works at 25,000 accounts.