ADR-0002: Infrastructure-as-Code Tooling and Compute Platform¶
Status¶
Proposed — refines the infrastructure choices in ADR-0001 (hosting, database) and settles the IaC tool that ADR-0001 left to the deployment guide. The application stack in ADR-0001 is unchanged. Awaiting client sign-off per project constraints non-negotiable #4 (stack is proposed, not confirmed).
Date¶
2026-07-01
Context¶
ADR-0001 locked the application architecture (Django + Wagtail + DRF on ECS, Postgres, Redis, S3/CloudFront, Cloudflare) but left two infrastructure specifics loosely pinned: it named ECS Fargate for compute and provisioned RDS Postgres 18 for the database, and early architecture drafts assumed Terraform for IaC. Before writing any infrastructure code we needed to settle:
- IaC tool — how the AWS resources are defined and deployed.
- Compute platform — Fargate vs EC2-backed ECS.
- Database deployment shape — provisioned RDS vs Aurora Serverless v2.
Constraints shaping the decision:
- Short runway (~14-week build from 2026-06-29, MVP ~Oct 2026) — favour a battle-tested scaffold over greenfield.
- Fresh AWS account, one available domain (
dev-cslewis.fueled.engineering); only a dev environment is provisioned now, prod is staged but not deployed. - Fueled's
copa-backendproject has a mature, in-production CDK scaffold the delivery lead pointed at as the reference implementation. - Traffic is read-heavy and spiky (Daily Drop), guest-accessible, cache-fronted.
Decision Drivers¶
- Must
- Reuse proven Fueled infrastructure patterns rather than build IaC from scratch inside the 14-week budget.
- Single-command deploy + rollback story a small team can operate.
- Least-privilege IAM, encrypted data stores, secrets in AWS Secrets Manager (never in code/images).
- Cloudflare in front with SSL Full (strict) — the origin must not be reachable directly.
- Should
- Dev/prod parity on the data engine (no behaviour drift between environments).
- Debuggable compute (shell access to hosts when diagnosing issues).
- Cost-efficient at MVP scale; scale-up path without re-architecture.
- Nice-to-have
- Phase 2 read replicas as a near one-line change.
- Typed IaC with editor autocomplete over stringly-typed config.
Considered Options¶
IaC tool¶
Option A: AWS CDK (TypeScript) — chosen¶
- Pros: Fueled's copa-backend ships a production-tested CDK scaffold (stacks for shared/network/compute/storage, per-env YAML, CodeBuild CI/CD, GitHub OIDC) we can adapt directly. Typed constructs, single
cdk deploy, integrates with the existingjustworkflow. Team familiarity. - Cons: Node toolchain alongside the Python app; CloudFormation drift/rollback quirks; a second language in the repo.
Option B: Terraform¶
- Pros: Cloud-agnostic, huge module ecosystem, the assumption in early architecture drafts.
- Cons: No existing Fueled scaffold for this shape; would be built from scratch. State-backend setup and management overhead. No reuse of copa's proven patterns — the exact cost the short runway can't absorb.
Compute platform¶
Option A: EC2-backed ECS (Auto Scaling Group) — chosen¶
- Pros: Matches copa's established, well-tested stack — direct reuse. Cheaper than Fargate at steady 24/7 load. Debuggable (SSH via EC2 Instance Connect onto the hosts). Flexible (instance types, host-level tuning). ECS still orchestrates; only the capacity provider is EC2.
- Cons: We own host patching/AMI lifecycle (mitigated by ECS-optimized AL2023 AMI + ASG rolling replacement). Capacity planning is manual-ish (ASG min/max).
Option B: ECS Fargate (ADR-0001's original)¶
- Pros: No host management, native autoscaling, less to patch.
- Cons: More expensive at steady load; no host shell access for debugging; diverges from the proven copa scaffold, which would mean adapting the reference implementation away from its tested shape.
Database deployment shape¶
Option A: Aurora Serverless v2 (PostgreSQL), both dev and prod — chosen¶
- Pros: One engine across environments — no dev/prod drift. ACU auto-scaling fits read-heavy, spiky Daily-Drop traffic. Read replicas are a one-line add for Phase 2. Faster failover than provisioned Multi-AZ RDS. Dev floors low (0.5 ACU) to stay cheap; prod floors at 1 ACU with a reader in a second AZ. Simplifies the storage stack (one code path).
- Cons: ACU-hour billing is less predictable than a fixed instance; Serverless v2 does not scale fully to zero (prod deliberately never does, to avoid cold-start latency).
Option B: Provisioned RDS Postgres (ADR-0001's original db.t4g.medium)¶
- Pros: Flat, predictable monthly cost; simplest mental model.
- Cons: Fixed capacity wastes money when idle and caps throughput under spikes; scaling means an instance-class change with downtime; read replicas are heavier to add. Would need a separate, smaller shape for dev — engine/config drift.
Decision¶
We will use AWS CDK (TypeScript), adapting the copa-backend scaffold into four stacks — shared (ECR + GitHub OIDC), network (VPC), compute (ECS on an EC2 ASG, ALB, CodeBuild, IAM), and storage (Aurora Serverless v2, ElastiCache Redis, S3) — under .aws/iac/, with per-environment config in config/{common,dev,prod}/*.yaml.
Compute is EC2-backed ECS; the database is Aurora Serverless v2 (PostgreSQL 17) in both environments. Only dev is deployed today; prod config is staged but not applied.
Edge/security posture: Cloudflare (orange-cloud proxy) → ACM public cert on the ALB → SSL Full (strict). The ALB security group restricts ingress to Cloudflare's published IP ranges so the origin cannot be reached directly. Authenticated Origin Pulls (mTLS) is documented as the stronger prod hardening step. Cloudflare Tunnel was considered and rejected as overkill for a standard ALB-fronted app.
Best-practice hardening applied over the baseline copa scaffold: IMDSv2 required on the launch template, ECR scan-on-push, ALB access logs to S3, ECS Container Insights, split least-privilege task vs execution IAM roles (S3 scoped to the uploads bucket ARN, not *), deployment circuit breaker with rollback, and encrypted data stores.
Celery is not provisioned — the compute stack ships the web service only. Worker/beat services are added when the first async workload lands (per project scope).
Consequences¶
Positive¶
- Reuses a production-tested Fueled scaffold — infra work is adaptation, not invention, inside the 14-week budget.
- Dev/prod parity on the database engine; behaviour is consistent across environments.
- Cheaper steady-state compute and debuggable hosts.
- Cloudflare-locked origin with SSL Full (strict) meets the stated security posture.
- Phase 2 read replicas and scale-up are near-trivial with Aurora Serverless v2.
Negative¶
- Node toolchain lives in the repo alongside Python (CDK is TypeScript).
- We own EC2 host lifecycle (AMI/patching) that Fargate would have abstracted away.
- ACU-hour billing is less predictable than a fixed RDS instance.
- CDK/CloudFormation rollback and drift edge cases must be understood by whoever operates deploys.
Risks & Mitigations¶
- Risk: EC2 host patching neglected → drift/vulnerabilities. → Mitigation: ECS-optimized AL2023 AMI + ASG rolling instance replacement; revisit managed patching if it becomes a burden.
- Risk: Cloudflare IP-range allowlist on the ALB SG goes stale as Cloudflare updates ranges. → Mitigation: ranges are a single documented constant sourced from cloudflare.com/ips-v4; move to Authenticated Origin Pulls (mTLS) for prod, which removes the IP-list dependency.
- Risk: Aurora ACU cost creeps under sustained load. → Mitigation: dev floors at 0.5 ACU; alarm on ACU utilization; caps set via
serverlessV2MaxCapacity. - Risk: task-def ownership split (CDK seeds the initial task def; CodeBuild overrides it from
.aws/codebuild/td-*.jsonon every deploy) causes env/secret changes to silently no-op. → Mitigation: documented in deployment.md — env/secret changes go in the JSON template and the CDK secrets map together.
Related¶
- ADR-0001 — backend architecture; this ADR refines its hosting + database choices and settles IaC tooling.
docs/guides/deployment.md— deploy/rollback runbook, environment bring-up order, Cloudflare + ACM setup.docs/architecture.md— updated system topology reflecting these choices.- Reference implementation: Fueled
copa-backend.aws/iacCDK scaffold.