Skip to content

Historical only (August 2026). Terraform for the retired AWS ECS/RDS stacks. Production runs on R940; see lastvet-gates/docs/deployment/R940_ENVIRONMENT_TOPOLOGY.md.

LastVet AWS infrastructure (Terraform)

Terraform for staging and prod API stacks on ECS Fargate + RDS Postgres 18 + EFS + ALB.

Dual URL is baked in:

  • lastvet-{env}/rds/masterMIGRATION_DATABASE_URL
  • lastvet-{env}/rds/lastvet_appDATABASE_APP_URL

Prerequisites

  1. AWS account with BAA accepted
  2. IAM access key + secret (not root)
  3. Tools: terraform >= 1.5, aws CLI v2, node 20+
brew install terraform awscli
  1. Export credentials in your shell (do not commit these):
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_REGION="us-west-2"

One-shot apply (bootstrap + staging + prod)

cd last.vet-api
./infra/scripts/apply-all.sh

Or per environment:

./infra/scripts/apply-environment.sh staging
./infra/scripts/apply-environment.sh prod

ECS desired_count defaults to 0 until an API image is pushed and app secrets are populated.

After Terraform apply

1. ACM DNS validation (Cloudflare)

cd infra/environments/staging && terraform output acm_validation_records
cd infra/environments/prod && terraform output acm_validation_records

Add the CNAME records in Cloudflare for each hostname (api-staging.last.vet, api.last.vet). HTTPS listener can be enabled after validation.

2. Point DNS at ALB (when ready)

Hostname Origin
api-staging.last.vet staging ALB DNS name (terraform output alb_dns_name)
api.last.vet prod ALB DNS name

Cloudflare proxy is fine. Use Full (strict) once ACM is issued.

3. Database migrate + lastvet_app password

RDS is private. Run from a machine that can reach it (bastion, or temporarily allow your IP, or ECS one-off task).

Normal path (image contains migration files):

./infra/scripts/post-apply-db.sh staging
./infra/scripts/post-apply-db.sh prod

Or from ECS after --push-only:

./infra/scripts/run-ecs-db-setup.sh staging

Workaround (avoid): injecting migration SQL via a one-off ECS task when the running image does not include the new migrations/*.sql files. Acceptable only when Docker is unavailable; leaves schema ahead of code until ./infra/scripts/deploy-api-image.sh rollout. Never mark security guards verified until live API verify passes. Details: docs/deployment/DEV_STAGING.md § ECS SQL injection.

4. App secrets

Copy Railway env vars into lastvet-{env}/app in Secrets Manager before scaling ECS.

5. Build and deploy API image

# staging example
ECR_URL=$(cd infra/environments/staging && terraform output -raw ecr_repository_url)
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin "$ECR_URL"
docker build --platform linux/amd64 -t "$ECR_URL:latest" .
docker push "$ECR_URL:latest"

Update container_image in terraform.tfvars and set desired_count = 1, then terraform apply.

Layout

infra/
  bootstrap/          # S3 state + DynamoDB lock (run once)
  modules/
    network/          # VPC, subnets, NAT, security groups
    database/         # RDS + dual DB secrets
    storage/          # EFS uploads volume
    compute/          # ECR, ECS, ALB, ACM
  stacks/api/         # Composes modules per environment
  environments/
    staging/
    prod/
  scripts/

Cost note

Each environment provisions VPC + NAT Gateway + RDS + ALB + EFS. Expect roughly $150–250/mo per environment at minimum sizes before traffic. Scale down by destroying staging when not in use.