Skip to main content

Vertex — AWS architecture

Status: Proposed · Region: eu-west-1 (Ireland)

Network architecture — production VPC

Network (common ttg-* layer, per environment)

The network, NAT instance and RDS instance form the common platform layer (ttg-<env>-*). Vertex owns only its application resources (vertex-<env>-*).

prodstage
VPCttg-prod-vpc · 10.30.0.0/16ttg-stage-vpc · 10.20.0.0/16
Public subnets (AZ a/b/c)10.30.0-2.0/2410.20.0-2.0/24
Private application subnets10.30.10-12.0/2410.20.10-12.0/24
Private data subnets10.30.20-22.0/2410.20.20-22.0/24

All nine subnets are created per VPC (subnets are free). Only AZ a runs resources today; AZ b/c are reserved for a future ALB, NAT gateway, additional tasks and RDS Multi-AZ. Stage and prod VPCs are not peered.

Route tables

Route tableSubnetsDestinationTarget
ttg-<env>-rt-publicpublic-a/b/c0.0.0.0/0Internet gateway
ttg-<env>-rt-appprivate-app-a/b/c0.0.0.0/0NAT instance ENI
ttg-<env>-rt-dataprivate-data-a/b/cVPC CIDRlocal only (no internet)

Components

ComponentSpecification
NAT instance ttg-<env>-natt4g.nano, Amazon Linux 2023 + iptables (no community AMI), Elastic IP (fixed egress IP), source/destination check disabled. Swap to NAT gateway by one variable.
ECS cluster / service vertex-<env>Fargate, x86_64, task 0.5 vCPU / 2 GB. prod desired 1; stage 0–1. Deployment circuit breaker with rollback; ECS Exec enabled.
Task containerscore :8020 (runs Alembic migrations on start, dependsOn identity healthy, IDENTITY_BASE_URL=http://localhost:8030) · identity :8030 (health /health) · cloudflared (official image, tunnel token only). All essential.
RDS ttg-<env>-postgresPostgreSQL 16, db.t4g.micro, 20 GB gp3, Single-AZ, encrypted, not public, rds.force_ssl=1, master password managed by RDS. DB subnet group = data-a + data-b (RDS requires two AZs).
RDS prod settingsDeletion protection on, final snapshot on delete, 7-day backups (requires the AWS paid plan).
RDS stage settings1-day backups, stoppable when idle.
LogsCloudWatch Logs /vertex/<env>/{core,identity,cloudflared}, 30-day retention.

Terraform layout (ttg-infra)

ttg-infra/
terraform/
modules/ network, nat, database, secrets, ecs, access
shared/prod ttg-prod-vpc, ttg-prod-nat, ttg-prod-postgres (state key shared/prod)
shared/stage ttg-stage-vpc, ttg-stage-nat, ttg-stage-postgres (state key shared/stage)
vertex/prod ECS service, roles, secrets — reads shared/prod outputs
vertex/stage same for stage
apps/vertex/cloudflare/wrangler.toml
.github/workflows/vertex-deploy.yml, stage-power.yml

App stacks read the shared layer's outputs (VPC id, subnets, RDS endpoint and security group), so an app change can never modify the common network or database. terraform plan / apply run locally by the TTG operator.

Kept open for later

  • NAT instance → NAT gateway per AZ (one variable)
  • Single AZ → multi-AZ tasks; RDS Single-AZ → Multi-AZ (one flag)
  • Tunnel → ALB + ACM certificate on thetaxgraph.com (public subnets already exist)
  • Single account → one AWS account per environment (modules take the account as input)
  • x86_64 → ARM64 after an arm64 build is proven on stage