Infrastructure as Code (IaC) for the NHS HomeTest Service using Terraform and Terragrunt for multi-environment, multi-account AWS deployments.
This repository manages the AWS infrastructure for the NHS HomeTest Service, including:
- Bootstrap β Terraform state backend (S3 + KMS) and GitHub OIDC for CI/CD
- Networking β VPC, subnets, NAT gateways, Network Firewall, VPC endpoints, Route53 with DNSSEC
- Shared Services β WAF, ACM certificates, KMS, Cognito, IAM roles, SNS alerts, Secrets Manager
- Aurora PostgreSQL β Serverless v2 database with Goose migrations
- ECS Cluster β Fargate cluster with shared ALB for WireMock and container workloads
- HomeTest Application β Lambda functions, API Gateway, CloudFront + S3 SPA, SQS queues, WireMock
graph TB
classDef aws fill:#FF9900,stroke:#232F3E,color:#232F3E,font-weight:bold
classDef network fill:#8C4FFF,stroke:#232F3E,color:#fff
classDef security fill:#DD344C,stroke:#232F3E,color:#fff
classDef compute fill:#ED7100,stroke:#232F3E,color:#fff
classDef storage fill:#3B48CC,stroke:#232F3E,color:#fff
classDef database fill:#3B48CC,stroke:#232F3E,color:#fff
classDef cdn fill:#8C4FFF,stroke:#232F3E,color:#fff
classDef messaging fill:#E7157B,stroke:#232F3E,color:#fff
classDef identity fill:#DD344C,stroke:#232F3E,color:#fff
classDef mgmt fill:#E7157B,stroke:#232F3E,color:#fff
classDef container fill:#ED7100,stroke:#232F3E,color:#fff
USER["π€ User<br/>{env}.poc.hometest.service.nhs.uk"]
subgraph AWS["βοΈ AWS Account (poc: 781863586270 / dev: 781195019563) β eu-west-2"]
subgraph BOOTSTRAP["π§ Bootstrap (deployed once)"]
S3STATE["π¦ S3<br/>Terraform State"]:::storage
KMSSTATE["π KMS<br/>State Encryption"]:::security
OIDC["π GitHub OIDC<br/>IAM Role"]:::identity
end
subgraph EDGE["π Edge Services"]
R53["π Route53<br/>hometest.service.nhs.uk<br/>DNSSEC + DNS Query Logging"]:::network
WAFCF["π‘οΈ WAF<br/>CloudFront"]:::security
WAFAPIGW["π‘οΈ WAF<br/>API Gateway / ALB"]:::security
ACM["π ACM<br/>*.poc.hometest.service.nhs.uk"]:::security
end
subgraph VPC["π VPC 10.0.0.0/16"]
subgraph PUBSUB["Public Subnets"]
NAT["π NAT Gateway"]:::network
NFW["π§± Network Firewall<br/>Domain + IP Filtering"]:::security
ALB["π ALB<br/>Shared (ECS)"]:::network
end
subgraph PRIVSUB["Private Subnets"]
subgraph ENV_DEV["π¦ Per-Environment (dev, uat, demo, prod)"]
CF["βοΈ CloudFront<br/>+ S3 SPA (Next.js)"]:::cdn
APIGW["π API Gateway<br/>REST API v1"]:::compute
L1["Ξ» eligibility-lookup"]:::compute
L2["Ξ» login"]:::compute
L3["Ξ» session"]:::compute
L4["Ξ» order-service"]:::compute
L5["Ξ» get-order"]:::compute
L6["Ξ» order-router<br/>(SQS-triggered)"]:::compute
L7["Ξ» order-result"]:::compute
L8["Ξ» get-results"]:::compute
L9["Ξ» order-status"]:::compute
L10["Ξ» postcode-lookup"]:::compute
SQS1["π¨ SQS<br/>order-placement"]:::messaging
SQS2["π¨ SQS<br/>notify-messages"]:::messaging
SQS3["π¨ SQS<br/>order-results"]:::messaging
SQS4["π¨ SQS FIFO<br/>notifications"]:::messaging
WM["π³ WireMock<br/>ECS Fargate<br/>(optional)"]:::container
end
VPCE["π VPC Endpoints<br/>S3, Lambda, SecretsManager,<br/>SQS, KMS, CloudWatch, ECR"]:::network
end
subgraph DATASUB["Data Subnets (isolated)"]
RDS["π Aurora PostgreSQL<br/>Serverless v2"]:::database
end
end
subgraph SHARED["π Shared Services"]
KMS["π KMS<br/>main + pii_data keys"]:::security
COGNITO["π₯ Cognito<br/>User Pool + Identity Pool"]:::identity
IAM["π€ Developer IAM<br/>Deploy Role"]:::identity
SM["ποΈ Secrets Manager<br/>Supplier Credentials"]:::security
SNS["π’ SNS<br/>Alerts Topic"]:::messaging
end
subgraph EXTERNAL["π External Services"]
NHSLOGIN["NHS Login<br/>(sandpit / prod)"]
OSPLACES["OS Places API<br/>(postcode lookup)"]
SUPPLIERS["Supplier APIs<br/>(Preventex, etc.)"]
end
end
USER -->|HTTPS| R53
R53 -->|DNS| CF
CF -->|"/* β S3 SPA"| APIGW
CF -.->|WAF| WAFCF
CF -.->|TLS| ACM
APIGW -->|"/eligibility-lookup/*"| L1
APIGW -->|"/login/*"| L2
APIGW -->|"/session/*"| L3
APIGW -->|"/order/* (POST)"| L4
APIGW -->|"/get-order/* (GET)"| L5
APIGW -->|"/result/*"| L7
APIGW -->|"/results/* (GET)"| L8
APIGW -->|"/test-order-status/*"| L9
APIGW -->|"/postcode-lookup/*"| L10
APIGW -.->|WAF| WAFAPIGW
SQS1 -->|trigger| L6
L1 & L5 & L7 & L8 & L9 -->|query| RDS
L4 -->|enqueue| SQS1
L7 & L9 -->|enqueue| SQS2
L2 -.->|auth| NHSLOGIN
L10 -.->|lookup| OSPLACES
L6 -->|HTTP| SUPPLIERS
L6 -.->|secrets| SM
L1 & L2 & L3 & L4 & L5 & L6 & L7 & L8 & L9 & L10 -->|egress| NAT
NAT -->|filtered| NFW
L1 & L2 & L3 & L4 & L5 & L6 & L7 & L8 & L9 & L10 -.->|encrypt| KMS
L1 & L2 & L3 & L4 & L5 & L6 & L7 & L8 & L9 & L10 -.->|private access| VPCE
ALB -->|host routing| WM
SNS -.->|alarms| SQS1 & SQS2 & SQS3 & SQS4
OIDC -.->|"CI/CD"| S3STATE
The following tools are managed via mise (see .mise.toml):
| Tool | Version | Purpose |
|---|---|---|
| Terraform | 1.14.8 | Infrastructure provisioning |
| Terragrunt | 1.0.0 | DRY Terraform configuration |
| AWS CLI | 2.34.26 | AWS interaction |
| Python | 3.14.2 | Git hooks, scripting |
| TFLint | 0.61.0 | Terraform linting |
| terraform-docs | 0.22.0 | Auto-generated documentation |
| Trivy | 0.69.3 | Security scanning |
| Checkov | 3.2.517 | Policy-as-code scanning |
| Gitleaks | 8.30.1 | Secret scanning |
| pre-commit | 4.5.1 | Git hooks |
| Go | 1.26.2 | Lambda goose migrator builds |
| goose | 3.27.0 | Database migrations |
| Vale | 3.14.1 | Prose linting |
Additional requirements:
- Docker or compatible container runtime
- GNU Make 3.82+
- jq (JSON processing)
- Firefox with AWS SSO Containers (optional, for multi-account browser management)
Install all tool versions:
mise installaws configure sso
# Resulting ~/.aws/config profile:
# [profile Admin-PoC]
# sso_session = nhs
# sso_account_id = 781863586270
# sso_role_name = Admin
# region = eu-west-2
#
# [sso-session nhs]
# sso_start_url = https://d-9c67018f89.awsapps.com/start/#
# sso_region = eu-west-2
# sso_registration_scopes = sso:account:access
aws sso login --profile Admin-PoC
export AWS_PROFILE=Admin-PoC# Clone the repository
git clone https://github.com/NHSDigital/hometest-mgmt-terraform.git
cd hometest-mgmt-terraform
# Install tool versions
mise install
# Configure pre-commit hooks and development dependencies
make configSee infrastructure/README.md for the full infrastructure guide including:
- Directory structure and module documentation
- Deployment order and dependencies
- Security features (WAF, KMS, VPC, Network Firewall)
- Troubleshooting guide
| Directory | Purpose |
|---|---|
infrastructure/src/ |
Terraform root modules (bootstrap, network, shared_services, aurora-postgres, ecs-cluster, hometest-app, lambda-goose-migrator, mock-service, rds-postgres) |
infrastructure/modules/ |
Reusable Terraform modules (api-gateway, cloudfront-spa, lambda, lambda-iam, aurora-postgres, sqs, sns, waf, developer-iam, deployment-artifacts) |
infrastructure/environments/ |
Terragrunt environment configurations (poc, dev accounts with core + per-env app stacks) |
scripts/ |
Build, test, and deployment helper scripts |
docs/ |
ADRs, developer guides, diagrams, user guides |
.github/workflows/ |
CI/CD pipelines |
# 1. Bootstrap (first time only β creates state backend)
cd infrastructure/src/bootstrap
terraform init && terraform apply
# 2. Deploy core (network β shared_services β aurora-postgres β ecs)
cd infrastructure/environments/poc/core/network
terragrunt apply
cd ../shared_services
terragrunt apply
cd ../aurora-postgres
terragrunt apply
cd ../ecs
terragrunt apply
# 3. Deploy application environment (app + lambda-goose-migrator)
cd ../../hometest-app/dev/lambda-goose-migrator
terragrunt apply
cd ../app
terragrunt applycd infrastructure/environments/poc
terragrunt run-all applyThe infrastructure supports multiple AWS accounts and environments:
| Account | Account ID | Environments |
|---|---|---|
| poc | 781863586270 | dev, uat, demo, prod, dev-example |
| dev | 781195019563 | staging |
Each environment under poc/hometest-app/{env}/ or dev/hometest-app/{env}/ contains:
env.hclβ environment name, domain overrides, feature flags (e.g., WireMock)app/terragrunt.hclβ hometest application stacklambda-goose-migrator/terragrunt.hclβ database migrations
Domain pattern: {env}.{account}.hometest.service.nhs.uk (e.g., dev.poc.hometest.service.nhs.uk)
Configured in .pre-commit-config.yaml:
terraform_fmt/terragrunt_fmtβ formattingterraform_tflintβ lintingterragrunt_validate_inputsβ input validation per stackterraform_checkovβ policy-as-codeterraform_docsβ auto-generate module docsgitleaksβ secret detectionmarkdownlintβ Markdown lintingsqlfluff-lint/sqlfluff-fixβ SQL lintingactionlintβ GitHub Actions lintingshellcheckβ shell script analysisyamllintβ YAML linting
# Run all checks
pre-commit run --all-files
# Or via mise task
mise run pre-commitmise run pre-commit # Run all pre-commit hooks
mise run test-migrations # Test Goose DB migrations against local PostgreSQL
mise run test-migrations-keep # Same, but keep the PostgreSQL container running
mise run tf-clean-cache # Remove .external_modules, .terragrunt-cache, .terraform.lock.hclmake test- Infrastructure Guide β full infrastructure documentation
- Creating a New Environment β step-by-step guide
- Developer Guides β Bash/Make, Docker, Terraform scripting
- User Guides β static analysis, Git hooks, secrets scanning
- ADRs β architecture decision records
- Terragrunt Live Stacks Example
- Terragrunt Catalog Example
- Terragrunt Documentation
- mise Version Manager
- NHS AWS SSO User Access
Unless stated otherwise, the codebase is released under the MIT License. This covers both the codebase and any sample code in the documentation.
Any HTML or Markdown documentation is Β© Crown Copyright and available under the terms of the Open Government Licence v3.0.