Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

60 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ACA-Project-v1

A Flask task-management (Todo) app deployed to Azure Container Apps behind an Application Gateway, provisioned end-to-end with Terraform and shipped through Azure DevOps CI/CD. Staging and production run as separate Terraform root configurations in isolated Azure subscriptions, both in UK South.

Architecture

Azure architecture

  • Applicationapp/: 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 and is not shared across replicas; there is no database.
  • Infrastructureterraform/: composed of network, container_app, dns, and app_gateway modules under terraform/modules/. The TLS certificate is issued into Key Vault by the app_gateway module (keyvault.tf).
  • Environmentsstaging and production are separate root configs with their own backend, provider, and lock file, under terraform/environments/. Per-environment settings (subscription, resource group, image, hostname, task sizing) live in each environment's terraform.tfvars.

Request path

Public DNS → Application Gateway public IP (in snet-appgw) → HTTPS listener terminating TLS with a Key Vault certificate → re-encrypted to the Container App's FQDN, which a private DNS zone resolves to the Container Apps environment's private static IP in snet-aca → Flask container on port 3000. Port 80 permanently redirects to HTTPS. The environment uses an internal load balancer, so nothing behind the gateway is publicly reachable.

Application

Routes: GET / (HTML UI), GET /health (App Gateway probe and CD smoke test), and CRUD under /tasks.

Run locally

cd app
pip install -r requirements-dev.txt
python app.py            # serves on http://localhost:3000

Then open http://localhost:3000, or exercise the API:

curl http://localhost:3000/health
curl -X POST http://localhost:3000/tasks -H 'Content-Type: application/json' -d '{"title":"Write docs"}'
curl http://localhost:3000/tasks

Tests and gates

pytest                                     # run all tests
pytest --cov=app --cov-report=term         # with coverage (as CI runs it)
ruff format --check .                      # formatting gate
ruff check .                               # lint gate
pip-audit -r requirements.txt              # dependency audit

Container

app/Dockerfile is a multi-stage build producing a hardened distroless image running as non-root UID 65532, exposing port 3000. There is no shell in the final image.

docker build -t task-management ./app
docker run -p 3000:3000 task-management

Infrastructure (Terraform)

Run from inside an environment directory — each has its own backend and state, so there is no workspace to select.

cd terraform/environments/staging     # or: terraform/environments/production
terraform init
terraform plan
terraform apply
terraform fmt -check                  # formatting gate used by the plan pipeline

State is stored in an Azure Storage account backend (providers.tf); terraform/bootstrap-backend.sh creates that storage account once, before the first init. To change sizing, hostnames, or images for an environment, edit that environment's terraform.tfvars. To change infrastructure shape, edit the module and both environment main.tf files.

Key outputs

app_gateway_public_ip, container_app_fqdn.

CI/CD

All pipelines live in pipelines/ and authenticate to Azure through service connections (azure-service-connection for staging, prod-service-connection for production).

Pipeline Trigger What it does
ci.yaml Push on main for app/** Ruff, pytest + coverage, pip-audit, gitleaks, Docker build + Trivy scan (fails on CRITICAL); pushes :latest and the commit SHA to the staging and production ACRs on non-PR pushes to main
cd.yaml Pipeline resource — after ci succeeds on main Resolves the image by digest, deploys to staging → smoke-tests https://staging.tflow.co.uk/health → deploys to production
terraform-plan.yaml PR via Branch Policy → Build Validation Format check, validate, and plan for both environments; posts rendered plans as PR comments
terraform-apply.yaml Push on main for terraform/** Applies staging, then production
terraform-destroy.yaml Manual dispatch Destroys a chosen environment

Deploys and applies are gated by Azure DevOps Environments — add an approval check on the production environment (Pipelines → Environments → production) to enforce manual approval.

The CD deployment targets (stagingContainerApp, stagingResourceGroup, containerName) are hard-coded in cd.yaml, not derived from Terraform outputs, so they must match the names Terraform actually creates (e.g. ca-taskflow-staging, staging-taskflow).

Repository layout

app/                       Flask application, tests, Dockerfile
  templates/, static/      Server-rendered UI
  tests/                   pytest suite
terraform/
  environments/            Per-environment root configs (staging, production)
  modules/                 network, container_app, dns, app_gateway
  bootstrap-backend.sh     One-time remote-state setup
pipelines/                 Azure DevOps CI, CD, and Terraform plan/apply/destroy
images/                    Architecture diagram

About

A Flask task-management (Todo) app deployed to Azure Container Apps behind an Application Gateway, provisioned end-to-end with Terraform and shipped through Azure DevOps CI/CD.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages