Skip to content

Infrastructure — Detailed Reference

This document describes every resource provisioned by this Terraform project.

Overview

The project currently deploys a staging environment for the Tracker REST API (tracker-restapi) on AWS in the eu-west-2 (London) region. The public endpoint is tracker.staging.glimpse.technology.

This document reflects the staging implementation. The production account is separate from staging and already contains the existing production VPC, TimescaleDB cluster, and Valkey. The tracker production stack should create its own VPC in that same production account and connect to the existing production VPC for database access.

Terraform state is stored remotely in an S3 bucket (bucket name supplied at init time via the backend "s3" partial configuration).

The stack is organised as one environment (envs/staging/) that composes eleven reusable modules (modules/*). Every resource is tagged with Project, Environment, and ManagedBy = "terraform" plus any caller-supplied extra tags.


Architecture at a Glance

Internet
  │
  ▼
WAF WebACL
  │
  ▼
Application Load Balancer  (public subnets, HTTPS with HTTP redirect)
  │          │          │
  ▼          ▼          ▼
api:8000  frontend:80  admin:80   ← ECS Fargate services (private subnets)
                                        │
                    ┌───────────────────┤
                    ▼                   ▼
              PostgreSQL 16          Valkey
              + TimescaleDB       (Redis-compat cache)
              + PostGIS              on same EC2 host
                   (single EC2 instance, private subnet)

Module Descriptions

1. KMS (modules/kms)

Creates a single Customer-Managed Key (CMK) that is used as the encryption key for every other service in the stack.

Resource Detail
aws_kms_key CMK with automatic annual key rotation enabled and a 7-day deletion window. The key policy grants full access only to the AWS account root principal.
aws_kms_alias Human-readable alias alias/<project>-<env> pointing to the key.

The KMS key ARN is passed into every other module so that S3, EBS, CloudWatch Logs, ECR, Secrets Manager, and MemoryDB all encrypt data with the same project key.


2. Network (modules/network)

Builds the Virtual Private Cloud and all routing primitives.

VPC

  • CIDR: 10.40.0.0/24 (staging default)
  • DNS hostnames and DNS support both enabled
  • Default security group is locked down — all ingress and egress rules removed
Subnets

The /24 is divided into four /26 blocks across two Availability Zones:

Tier AZ 0 AZ 1
Public .0.0/26 .64.0/26
Private .128.0/26 .192.0/26

map_public_ip_on_launch = false on all subnets; no instance gets a public IP automatically.

Internet connectivity
  • aws_internet_gateway — attached to the VPC, used by the public route table.
  • aws_eip + aws_nat_gateway — a single NAT Gateway in the first public subnet gives private-subnet resources outbound internet access without a public IP.
  • Public route table: 0.0.0.0/0 → IGW, associated with both public subnets.
  • Private route table: 0.0.0.0/0 → NAT GW, associated with both private subnets.
VPC Flow Logs
  • All traffic (ALL) captured to a CloudWatch log group /aws/vpc/<prefix>-flow-logs
  • Log retention: 365 days; logs encrypted with the project KMS key
  • Dedicated IAM role grants logs:CreateLogStream, logs:PutLogEvents, logs:DescribeLogStreams scoped to that log group

3. Security Groups (modules/security)

Four security groups implement a strict, least-privilege network perimeter. All groups are in the project VPC.

ALB security group (edge tier)

  • Inbound: TCP 80 and TCP 443 from 0.0.0.0/0
  • Outbound: TCP 80 to the ECS security group (frontend/admin containers) and TCP 8000 to the ECS security group (API container)

ECS security group (application tier)

  • Inbound: TCP 80 and TCP 8000 from the ALB security group only
  • Outbound: TCP 5432 to the database security group; TCP 6379 to the MemoryDB/Valkey security group

Database security group (data tier)

  • Inbound: TCP 5432 (PostgreSQL) from the ECS security group only
  • Outbound: none

MemoryDB/Cache security group (data tier)

  • Inbound: TCP 6379 (Valkey) from the ECS security group only
  • Outbound: none

4. ECR (modules/ecr)

Creates four Elastic Container Registry repositories for the application images:

Key Repository name
api tracker-api
frontend tracker-frontend
admin tracker-admin
anisette tracker-anisette

Each repository is configured with:

  • Image tag immutability — existing tags cannot be overwritten
  • KMS encryption using the project CMK
  • Scan on push — vulnerability scanning runs automatically on every docker push
  • Lifecycle policy — once more than 30 images exist (any tag status), the oldest are expired automatically

5. ACM (modules/acm)

Requests an ACM TLS certificate for tracker.staging.glimpse.technology using DNS validation.

The aws_acm_certificate_validation resource waits until all DNS CNAME records are present before marking the certificate as valid. The required CNAME values are surfaced via the acm_validation_records output so they can be added to Cloudflare (or another DNS provider) manually or via a separate DNS pipeline.

create_before_destroy = true ensures certificate renewals do not interrupt the ALB listener.


6. WAF (modules/waf)

Deploys a regional WAFv2 Web ACL attached to the ALB.

Rule Priority Purpose
AWSManagedRulesCommonRuleSet 10 Blocks common web exploits (SQLi, XSS, bad user-agents, etc.)
AWSManagedRulesKnownBadInputsRuleSet 20 Blocks known malicious request patterns and Log4Shell/SSRF probes

Default action is allow (traffic not matching a blocking rule passes through).

CloudWatch metrics and sampled requests are enabled for both rules. WAF logs are written to /aws/waf/<project>-<env> (365-day retention, KMS-encrypted).


7. ALB (modules/alb)

Provisions the internet-facing Application Load Balancer and all of its listeners, target groups, and access-log storage.

S3 access-log bucket

  • Bucket name: glimpse-<project>-<env>-alb-logs-<account-id>
  • All public access blocked
  • Server-side encryption with the project KMS key
  • Versioning enabled
  • Lifecycle rule: expire objects after 90 days; abort incomplete multipart uploads after 7 days
  • Bucket policy allows the ELB log-delivery service to write under the configured prefix

Load balancer

  • Internet-facing, application type
  • Placed in both public subnets
  • Attached to the ALB security group
  • drop_invalid_header_fields = true
  • Deletion protection enabled (prevents accidental terraform destroy of the ALB)
  • WAF WebACL attached via aws_wafv2_web_acl_association

Listeners and routing

Listener Port Action
HTTP 80 Permanent 301 redirect → HTTPS:443
HTTPS 443 Forward to frontend target group (default)

HTTPS listener rules (evaluated in priority order):

Priority Path pattern Target group
10 /api/*, /api/v1/*, /docs, /redoc, /openapi.json api (port 8000)
20 /admin*, /health* admin (port 80)
Everything else frontend (port 80)

Target groups

  • all use ip target type (required for Fargate):
Group Port Health-check path Thresholds
api 8000 /api/v1/health 2 healthy / 2 unhealthy, 5 s timeout, 30 s interval
frontend 80 / Same
admin 80 / Same

8. Database (modules/database)

Provisions a single EC2 instance running both PostgreSQL 16 and Valkey inside a private subnet. This is the data tier for staging; no managed RDS or ElastiCache is used at this tier.

EC2 instance configuration

  • AMI: latest Debian 12 (amd64 HVM, EBS-backed) from the official Debian AWS account (136693071363)
  • Instance type: t3.medium (default)
  • Placed in a single private subnet with no public IP
  • Root volume: 50 GiB gp3, KMS-encrypted, deleted on termination
  • IMDSv2 enforced (http_tokens = "required", hop limit 2)
  • Detailed CloudWatch monitoring enabled

IAM role

  • AmazonSSMManagedInstanceCore policy attachment — allows AWS Systems Manager Session Manager access (no SSH bastion required)
  • Inline policy: secretsmanager:GetSecretValue and secretsmanager:DescribeSecret on the postgres and redis/valkey secrets; kms:Decrypt and kms:DescribeKey on the project CMK

Bootstrap user-data script (user-data.sh.tftpl)

Runs once on first boot:

  1. Installs the official PostgreSQL 16 apt repository and TimescaleDB repository
  2. Installs: postgresql-16, postgresql-contrib-16, timescaledb-2-postgresql-16, postgresql-16-postgis-3
  3. Fetches postgres_password and redis_password from Secrets Manager using aws secretsmanager get-secret-value
  4. Configures PostgreSQL:
  5. listen_addresses = '*'
  6. shared_preload_libraries = 'timescaledb,pg_stat_statements'
  7. max_connections = 100, shared_buffers = 256MB, effective_cache_size = 768MB
  8. pg_hba.conf restricted to scram-sha-256 auth within the VPC CIDR only
  9. Creates the tracker role and the tracker database
  10. Enables extensions: timescaledb, postgis, postgis_topology, pg_stat_statements
  11. Installs Valkey (falls back to redis-server if the Valkey package is unavailable)
  12. Configures Valkey: binds 0.0.0.0, password authentication, AOF persistence enabled

9. ECS (modules/ecs)

Creates an ECS Fargate cluster and runs three container services.

Cluster

  • Container Insights enabled (enhanced CloudWatch metrics)

IAM roles

  • Execution role — used by the ECS agent to pull images from ECR and fetch secrets from Secrets Manager. Has AmazonECSTaskExecutionRolePolicy plus an inline policy granting KMS decrypt for the project CMK.
  • Task role — attached to running containers (currently grants no extra permissions; extend as needed).

Services deployed

Service Image CPU Memory Port
api tracker-api:<git-sha> from ECR 512 1024 MB 8000
frontend tracker-frontend:<git-sha> from ECR 256 512 MB 80
admin tracker-admin:<git-sha> from ECR 256 512 MB 80
services tracker-services:<git-sha> from ECR 256 512 MB -
anisette tracker-anisette:<git-sha> from ECR 256 512 MB 6969

The api, frontend, and admin services run on Fargate, in private subnets, with assign_public_ip = false. Each service has desired_count = 1. Rolling deployments allow 50 % minimum healthy / 200 % maximum.

The shared services image runs the TaskiQ worker processes. ECS should start separate services from the same image with different command overrides rather than building one image per worker role. In staging, those worker services are scaffolded behind the enable_workers Terraform flag so the image and runtime shape can be validated before they are turned on. Only tracker-fetcher-2 needs the shared /data EFS mount and ANISETTE_SERVER; the other worker services remain stateless.

Anisette is also deployed on Fargate as a private-only service. It is registered in private service discovery as anisette-v3.anisette-v3.local and is reachable only from inside the VPC on port 6969. It mounts an EFS-backed persistent volume at /data so its config/state survives task replacement. EFS is a managed network filesystem for live runtime storage; it is not S3-backed object storage.

API container environment variables (injected at task launch)

Variable Value
POSTGRES_SERVER EC2 private DNS of the database host
POSTGRES_USER tracker
POSTGRES_DB tracker
POSTGRES_PORT 5432
REDIS_HOST EC2 private DNS of the database host
REDIS_PORT 6379
REDIS_CLUSTER_MODE false
REDIS_TLS_ENABLED false
REDIS_CACHE_TTL 3600
REDIS_CACHE_DB / TASKS_DB / HEALTH_DB / NOTIFICATIONS_DB 0 / 1 / 2 / 3
ANISETTE_SERVER http://anisette-v3.anisette-v3.local:6969

Secrets injected via Secrets Manager (not stored in environment variables)

Variable Secret
POSTGRES_PASSWORD postgres_password secret
SECRET_KEY secret_key secret
REDIS_PASSWORD redis_password secret

Logging

— all containers use the awslogs driver, writing to the CloudWatch log groups created by the logs module.


10. CloudWatch Logs (modules/logs)

Creates one CloudWatch log group per service:

Key Log group path
api /aws/ecs/<project>-<env>/api
frontend /aws/ecs/<project>-<env>/frontend
admin /aws/ecs/<project>-<env>/admin
anisette /aws/ecs/<project>-<env>/anisette

All groups are KMS-encrypted with the project CMK. Retention defaults to 90 days (configurable via the retention_in_days variable).


11. Secrets Manager (modules/secrets)

Creates three Secrets Manager secrets, each encrypted with the project CMK:

Logical name Purpose
postgres_password PostgreSQL tracker user password
secret_key Application signing/JWT secret key
redis_password Valkey authentication password

For each secret, Terraform generates a 32-character random password (mixed case, digits, and special characters from !@#$%^&*()-_=+[]{}:,.?) and stores it as the initial secret value. Rotation is not automated (noted in a code comment as a future enhancement).


12. MemoryDB (modules/memorydb) — module present, not used in staging

The codebase includes a full MemoryDB module (Valkey-compatible managed cluster). It is not instantiated in envs/staging/main.tf; the staging data tier uses Valkey running on the database EC2 host instead. The module is available for environments that require a managed, cluster-mode cache.


Dependency Graph (simplified)

kms
 ├── network (flow log encryption)
 ├── ecr (image encryption)
 ├── waf (log encryption)
 ├── secrets (secret encryption)
 ├── logs (log encryption)
 ├── alb (S3 log bucket encryption)
 ├── database (EBS encryption + secret access)
 └── ecs (execution role KMS decrypt)

network → security → alb → ecs
network → database
secrets → database (secret ARNs in user-data)
secrets → ecs (secret ARNs injected into containers)
acm → alb (TLS certificate)
waf → alb (WebACL association)
logs → ecs (log group names passed to task definitions)
ecr → ecs (image URLs)
alb → ecs (target group ARNs)

Key Variables (staging defaults)

Variable Default Description
project_name tracker-restapi Prefix for all resource names
environment staging Environment label
aws_region eu-west-2 AWS region (London)
vpc_cidr 10.40.0.0/24 VPC address space
public_hostname tracker.staging.glimpse.technology Public DNS name
enable_anisette false Toggle for the optional private Anisette ECS service
secret_names (required) Secrets Manager path strings for postgres, secret_key, redis

Notable Security Properties

  • No SSH access — the database host is managed exclusively through SSM Session Manager.
  • IMDSv2 enforced — instance metadata requires a signed token; hop limit 2 supports containers.
  • No public IPs on ECS or database — all application traffic enters via the ALB only.
  • KMS encryption everywhere — EBS, ECR, CloudWatch Logs, S3, Secrets Manager all use the same project CMK with annual rotation.
  • WAF in front of ALB — common exploit patterns and known bad inputs are blocked before reaching application code.
  • Immutable ECR tags — once an image tag is pushed it cannot be overwritten, preventing tag-hijack attacks.
  • ALB deletion protection — prevents accidental teardown of the load balancer.
  • Default VPC security group locked — prevents any resource from accidentally inheriting the permissive default rules.