The problem
Engineers were spending hours per branch wiring up an AWS-side environment to test their PR. The team had ~14 active developers; the queue was a meeting, not a system. Iris turned the entire flow into a self-service API call: push to a PR, get back a stable URL.
Architecture
Iris is a control-plane service — it doesn't serve user traffic, it orchestrates AWS resources. One FastAPI process, one Postgres database, and a reconciliation loop holding it all together.
- 1 CloneRepo clone with on-disk cache to skip cold checkouts.
- 2 BuildAWS CodeBuild kicks off a Docker image build, tagged with the env-specific IMAGE_TAG.
- 3 PushImage goes to ECR; cleanup keeps the repo from drifting.
- 4 RunECS Fargate task spins up with the freshly built image.
- 5 RouteALB host-header routing rule attached to a shared listener — no per-env load balancer.
- 6 DNSRoute53 record created (or updated in place) to keep the URL stable.
- 7 TrackLifecycle row in Postgres with stage timestamps. Survives process restarts.
- 8 ReconcileBackground loop polls ECS state every 30s and reconciles the DB.
Three Core Decisions
1. Host-header routing on a shared ALB, not one ALB per env
The naive design is one ALB per environment. That's clean but the cost is linear in env count — at 14 developers iterating you're paying for ~30 ALBs at any given moment. Iris uses a single shared ALB with one listener and a host-header rule per environment. Cost is essentially flat in env count; latency is unchanged.
2. URL stability across re-creates
A PR is iterative. The most-iterated environment in the dataset absorbed 222 rebuilds at 100% success over its lifetime. If every rebuild churned a new URL, every Jira link and reviewer bookmark would rot.
So Iris detects existing ALB rules and Route53 records on collision and updates them in place. {env}.dev.internal.example stays valid across the full PR lifecycle. This single design call is what made 9.7 average rebuilds per env (p95 37, peak 222) practical instead of chaos.
3. Reconciliation loop survives process restarts
The in-memory active_deployments dict is fast but volatile. Postgres is the durable record, and an EnvironmentMonitor boot scan plus a TaskLifecycleService reconciler that polls ECS every 30s closes the gap. Restart Iris and lifecycle tracking picks up exactly where it left off — no orphan tasks, no leaked routing rules.
What it ran in 12 months
- 135 environments fully provisioned end-to-end (DB-tracked, post-cleanup) over 161 envs that ever ran a build.
- 1,567 CodeBuild image builds — average 9.7 rebuilds per env, p95 37, peak 222 on a single env.
- 98.5% image-build success across all 1,567 builds.
- 88.1% end-to-end provisioning success across the 8-stage pipeline.
- 29 unique developers; 73 unique branches; 66 Jira tickets accelerated.
- 93.3% of provisioned environments linked back to a Jira ticket.
- Monthly active developers: 2 (Sep 2025) → 14 (Apr 2026) — 7× growth.
- Conservatively, ~270 dev-hours saved at 2h/env.
Rebuild distribution
Half the envs are one-shot. A third rebuild 6–20 times. Fifteen rebuild more than 20 times. That long tail is the proof that the system absorbs real iterative dev workflows — not just demos that get spun up once for a screenshot.
Stack
- FastAPI control plane, Postgres for lifecycle state
- AWS CodeBuild for image builds, ECR for storage
- ECS Fargate for runtime, shared ALB with host-header routing
- Route53 for stable per-env DNS
- Secrets Manager for per-env secrets
- CloudWatch for build/runtime metrics