A Flask task-management (Todo) app deployed to AWS ECS Fargate behind an Application Load Balancer, provisioned end-to-end with Terraform and shipped through GitHub Actions CI/CD. Staging and production run as isolated Terraform workspaces in a single AWS account and region (eu-west-2).
- Application — app/: a Flask REST API with a small server-rendered UI. Task state is held in memory (a module-level dict), so it resets on restart; there is no database.
- Infrastructure — terraform/: composed of
vpc,alb,ecs,ecr,certificate, anddnsmodules under terraform/modules/. - Environments —
stagingandproductionare Terraform workspaces. Per-environment settings (CIDRs, task sizing, image URL, hostname) live in theworkspace_configmap in terraform/locals.tf.
cd app
pip install -r requirements-dev.txt
python app.py # serves on http://localhost:3000Then open http://localhost:3000, or exercise the API:
app/Dockerfile is a multi-stage build producing a hardened distroless image running as a non-root user, exposing port 3000.
docker build -t task-management ./app
docker run -p 3000:3000 task-managementRun everything from terraform/. Select a workspace before any plan/apply — an unrecognized workspace fails on the missing config key.
cd terraform
terraform init
terraform workspace select staging # or: production
terraform plan
terraform applyState is stored in an S3 backend with native lockfile locking (terraform/backend.tf). To change sizing, CIDRs, or hostnames for an environment, edit the workspace_config map in terraform/locals.tf rather than adding tfvars files.
app_url, alb_dns_name, vpc_id, ecs_cluster_id, ecs_service_name.
All workflows (.github/workflows/) authenticate to AWS via OIDC (no long-lived credentials).
| Workflow | Trigger | What it does |
|---|---|---|
| ci.yaml | PR / push on app/** |
Ruff, pytest + coverage, pip-audit, gitleaks, Docker build + Trivy scan; pushes the image to ECR on push to main |
| cd.yaml | After CI succeeds on main |
Deploy to staging → smoke-test /health → deploy to production |
| terraform-plan.yaml | PR on terraform/** |
Plans staging + production, posts plans as PR comments, runs Checkov |
| terraform-apply.yaml | Push on terraform/** |
Applies staging, then production |
| terraform-destroy.yaml | Manual dispatch | Destroys a chosen environment |
Production deploys and applies are gated by GitHub Environment protection rules — add required reviewers on the production environment to enforce manual approval.
app/ Flask application, tests, Dockerfile
templates/, static/ Server-rendered UI
tests/ pytest suite
terraform/ Root module + backend/providers/locals
modules/ vpc, alb, ecs, ecr, certificate, dns
.github/workflows/ CI, CD, and Terraform plan/apply/destroy pipelines

