Terraform¶
Overview¶
Infrastructure is managed with Terraform (>= 1.6.0) using the AWS provider (~> 5.0). State is stored remotely in S3 with file-based locking and encryption.
Each environment lives in its own AWS account and has its own state file. Changes are currently applied locally — CI-based apply is planned but not yet implemented.
Repository Structure¶
infra/
├── environments/
│ ├── prod/
│ │ ├── eu-central-1/ # Production core, vault, edge, atrax-api
│ │ └── eu-west-1/ # Production atrax-node (region-isolated)
│ └── stage/
│ ├── eu-central-1/ # Staging core, vault, atrax-api
│ └── eu-west-1/ # Staging atrax-node
└── modules/
├── core/ # Core-group services
│ ├── core-api/
│ ├── atrax-api/ # atrax controller, lives on the core cluster
│ ├── ecr/ # core-group ECR repos (atrax-api image)
│ └── ecs/ # core ECS cluster (also hosts atrax-api)
├── atrax/ # atrax-node only (region-isolated workers)
│ ├── ecr/ # atrax-node ECR repo (eu-west-1)
│ ├── ecs/ # atrax-node cluster: fixed EC2s + per-host EIPs
│ └── node/ # atrax-node ECS service / task def
├── edge/ # Edge CDN, Lambda@Edge, consent queue
└── vault/ # Vault analytics pipeline
├── clickhouse/
├── ecs/
├── etl/
├── grafana/
└── vault-api/
Each environments/<env>/<region>/ directory is an independent root module with its own backend configuration. You run
terraform init and terraform apply from within that directory.
State Backend¶
State is stored in per-account S3 buckets with encryption and locking:
| Environment | Region | Bucket | Key |
|---|---|---|---|
| prod | eu-central-1 | cookiehub-terraform-state-prod-759286286879 |
prod/eu-central-1/terraform.tfstate |
| prod | eu-west-1 | cookiehub-terraform-state-prod-759286286879 |
prod/eu-west-1/terraform.tfstate |
| stage | eu-central-1 | cookiehub-terraform-state-stage-258618559895 |
stage/eu-central-1/terraform.tfstate |
| stage | eu-west-1 | cookiehub-terraform-state-stage-258618559895 |
stage/eu-west-1/terraform.tfstate |
The state buckets themselves both live in eu-central-1 regardless of which region's resources they describe — one bucket per environment, multiple state keys for multi-region environments.
Both use use_lockfile = true and encrypt = true (AES256).
Naming Convention¶
All resource names follow the pattern {env}-{region_short}-{group}-{component}:
This is generated from locals.tf in each root module:
locals {
region_short = {
eu-central-1 = "euc1"
eu-west-1 = "euw1"
}
name_prefix = "${var.environment}-${local.region_short}-${var.group}"
base_tags = {
Environment = var.environment
Region = var.region
Group = var.group
ManagedBy = "terraform"
}
}
The name_prefix and base_tags are passed down to every module.
Module Conventions¶
Each module follows a standard file layout:
| File | Purpose |
|---|---|
main.tf |
Resource definitions |
variables.tf |
Input variables |
outputs.tf |
Output values |
versions.tf |
Provider constraints |
data.tf |
Data sources (when needed) |
Modules are small and purpose-specific. A typical service module manages:
- ECS task definition (with SSM secrets)
- ALB target group and listener rules
- Route53 DNS record
- IAM roles and security group
- CloudWatch log group
All modules receive common variables: name_prefix, environment, region, group, vpc_id, subnet_ids, and
base_tags.
How to Apply Changes¶
Prerequisites¶
- AWS CLI configured with named profiles for each account
- Terraform >= 1.6.0 installed
Workflow¶
# 1. Set the right AWS profile
export AWS_PROFILE=stage # or prod
# 2. Navigate to the environment
cd environments/stage/eu-central-1
# 3. Initialize (first time or after adding modules)
terraform init
# 4. Review changes
terraform plan
# 5. Apply
terraform apply
Manual applies only
There is no CI pipeline for Terraform yet. All changes are applied locally. Always run
terraform plan first and review the output carefully before applying.
Environment Differences¶
| Production | Stage | |
|---|---|---|
| AWS Account | 759286286879 | 258618559895 |
| Regions | eu-central-1 + eu-west-1 (atrax-node) | eu-central-1 + eu-west-1 (atrax-node) |
| Vault services | Yes | Yes |
| Atrax services | Yes (api in eu-central-1, node in eu-west-1) | Yes (same shape as prod) |
| Core API | Not yet (TF stubbed, commented out) | Yes |
| Public ALB | Yes (atrax-api.cookiehub.net) + internal |
Yes (multiple hosts) |
| Core ECS host | t3.small | t3.small |
| Atrax ECS host | r6a.xlarge (1 instance, 2 tasks) | t3.medium (1 instance, 1 task) |
What's Not in Terraform¶
Some resources were created manually and don't have corresponding .tf files:
- GitHub Actions OIDC provider and deploy roles — created via console.
- Pre-existing S3 buckets in prod (e.g.
cookiehub-cdn,cookiehub-consent,cookiehub-s3-access-logs) and theatrax-stage-*buckets in stage — referenced bydatasources or by ARN in policies, but not adopted under TF management.