Skip to main content

Overview

This guide walks through deploying LatentGEO to AWS in a production-ready configuration with:
  • ECS (Fargate) - Container orchestration
  • RDS PostgreSQL - Managed database (replacing Supabase)
  • ElastiCache Redis - Managed caching and queues
  • Application Load Balancer - Traffic distribution
  • CloudFront - CDN for frontend assets
  • WAF - Web Application Firewall
  • Secrets Manager - Credential management
This deployment follows the AWS Well-Architected Framework with multi-AZ redundancy, auto-scaling, and comprehensive monitoring.

Deployment Timeline

The complete deployment process takes approximately 8 weeks across 13 phases. Budget approximately $300/month for production infrastructure.

Prerequisites

1

AWS Account Setup

  • Create AWS account
  • Enable MFA on root account
  • Create IAM user for CI/CD with appropriate permissions
  • Configure AWS CLI with credentials
2

Generate Secure Secrets

Generate all required secrets before deployment:
# SECRET_KEY
python -c "import secrets; print(secrets.token_urlsafe(32))"

# ENCRYPTION_KEY
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

# Database password
python -c "import secrets; print(secrets.token_urlsafe(24))"
3

Domain Registration

  • Register domain (Route53 or external registrar)
  • Create hosted zone in Route53
  • Request SSL certificate in ACM
  • Validate certificate via DNS or email
4

Pre-deployment Validation

Run the release gate checks:
# Frontend checks
pnpm --dir frontend lint
pnpm --dir frontend run format:check
pnpm --dir frontend run type-check
pnpm --dir frontend test:ci
STRICT_BUILD=1 pnpm --dir frontend build

# Backend checks
python -m ruff check backend/app
python -m mypy backend/app --ignore-missing-imports
python -m bandit -r backend/app -q
pytest -q backend/tests -m "not integration and not live"

Infrastructure Setup

Phase 1: VPC and Networking

1

Create VPC

Create a VPC with CIDR block 10.0.0.0/16:
aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16 \
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=auditor-vpc}]'
2

Create Subnets

Create public and private subnets in two availability zones:
# Public subnet AZ1
aws ec2 create-subnet \
  --vpc-id vpc-xxxxx \
  --cidr-block 10.0.1.0/24 \
  --availability-zone us-east-1a

# Public subnet AZ2
aws ec2 create-subnet \
  --vpc-id vpc-xxxxx \
  --cidr-block 10.0.2.0/24 \
  --availability-zone us-east-1b

# Private subnet AZ1
aws ec2 create-subnet \
  --vpc-id vpc-xxxxx \
  --cidr-block 10.0.11.0/24 \
  --availability-zone us-east-1a

# Private subnet AZ2
aws ec2 create-subnet \
  --vpc-id vpc-xxxxx \
  --cidr-block 10.0.12.0/24 \
  --availability-zone us-east-1b
3

Configure Gateways

# Internet Gateway
aws ec2 create-internet-gateway
aws ec2 attach-internet-gateway --vpc-id vpc-xxxxx --internet-gateway-id igw-xxxxx

# NAT Gateway (for private subnets)
aws ec2 create-nat-gateway \
  --subnet-id subnet-public-xxxxx \
  --allocation-id eipalloc-xxxxx
4

Configure Route Tables

# Public route table
aws ec2 create-route \
  --route-table-id rtb-public-xxxxx \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-xxxxx

# Private route table
aws ec2 create-route \
  --route-table-id rtb-private-xxxxx \
  --destination-cidr-block 0.0.0.0/0 \
  --nat-gateway-id nat-xxxxx

Phase 2: Security Groups

1

ALB Security Group

Allow HTTPS and HTTP traffic:
aws ec2 create-security-group \
  --group-name auditor-alb-sg \
  --description "Security group for ALB" \
  --vpc-id vpc-xxxxx

# Allow HTTPS
aws ec2 authorize-security-group-ingress \
  --group-id sg-alb-xxxxx \
  --protocol tcp --port 443 --cidr 0.0.0.0/0

# Allow HTTP (for redirect)
aws ec2 authorize-security-group-ingress \
  --group-id sg-alb-xxxxx \
  --protocol tcp --port 80 --cidr 0.0.0.0/0
2

ECS Security Group

Allow traffic from ALB only:
aws ec2 create-security-group \
  --group-name auditor-ecs-sg \
  --description "Security group for ECS tasks" \
  --vpc-id vpc-xxxxx

aws ec2 authorize-security-group-ingress \
  --group-id sg-ecs-xxxxx \
  --protocol tcp --port 8000 \
  --source-group sg-alb-xxxxx
3

RDS Security Group

Allow PostgreSQL from ECS only:
aws ec2 create-security-group \
  --group-name auditor-rds-sg \
  --description "Security group for RDS" \
  --vpc-id vpc-xxxxx

aws ec2 authorize-security-group-ingress \
  --group-id sg-rds-xxxxx \
  --protocol tcp --port 5432 \
  --source-group sg-ecs-xxxxx
4

ElastiCache Security Group

Allow Redis from ECS only:
aws ec2 create-security-group \
  --group-name auditor-redis-sg \
  --description "Security group for ElastiCache" \
  --vpc-id vpc-xxxxx

aws ec2 authorize-security-group-ingress \
  --group-id sg-redis-xxxxx \
  --protocol tcp --port 6379 \
  --source-group sg-ecs-xxxxx

Phase 3: Database (RDS PostgreSQL)

1

Create DB Subnet Group

aws rds create-db-subnet-group \
  --db-subnet-group-name auditor-db-subnet-group \
  --db-subnet-group-description "Subnet group for RDS" \
  --subnet-ids subnet-private-1 subnet-private-2
2

Create RDS Instance

aws rds create-db-instance \
  --db-instance-identifier auditor-db \
  --db-instance-class db.t3.small \
  --engine postgres \
  --engine-version 16.1 \
  --master-username auditor \
  --master-user-password YOUR_SECURE_PASSWORD \
  --allocated-storage 20 \
  --storage-encrypted \
  --vpc-security-group-ids sg-rds-xxxxx \
  --db-subnet-group-name auditor-db-subnet-group \
  --multi-az \
  --backup-retention-period 30 \
  --preferred-backup-window "03:00-04:00" \
  --preferred-maintenance-window "mon:04:00-mon:05:00"
3

Enable Auto-scaling Storage

aws rds modify-db-instance \
  --db-instance-identifier auditor-db \
  --max-allocated-storage 1000 \
  --apply-immediately
4

Note the Endpoint

aws rds describe-db-instances \
  --db-instance-identifier auditor-db \
  --query 'DBInstances[0].Endpoint.Address' \
  --output text
Save this endpoint for DATABASE_URL configuration.

Phase 4: Cache (ElastiCache Redis)

1

Create Cache Subnet Group

aws elasticache create-cache-subnet-group \
  --cache-subnet-group-name auditor-cache-subnet-group \
  --cache-subnet-group-description "Subnet group for ElastiCache" \
  --subnet-ids subnet-private-1 subnet-private-2
2

Create Redis Cluster

aws elasticache create-replication-group \
  --replication-group-id auditor-redis \
  --replication-group-description "Redis for LatentGEO" \
  --engine redis \
  --cache-node-type cache.t3.small \
  --num-cache-clusters 2 \
  --automatic-failover-enabled \
  --at-rest-encryption-enabled \
  --transit-encryption-enabled \
  --cache-subnet-group-name auditor-cache-subnet-group \
  --security-group-ids sg-redis-xxxxx
3

Note the Endpoint

aws elasticache describe-replication-groups \
  --replication-group-id auditor-redis \
  --query 'ReplicationGroups[0].NodeGroups[0].PrimaryEndpoint.Address' \
  --output text
Save this endpoint for REDIS_URL configuration.

Phase 5: Secrets Manager

1

Create Secret for Credentials

aws secretsmanager create-secret \
  --name auditor-geo/prod \
  --description "Production credentials for LatentGEO" \
  --secret-string '{
    "DATABASE_URL": "postgresql+psycopg2://auditor:PASSWORD@rds-endpoint:5432/auditor_db",
    "REDIS_URL": "redis://redis-endpoint:6379/0",
    "SECRET_KEY": "your-secret-key",
    "ENCRYPTION_KEY": "your-encryption-key",
    "BACKEND_INTERNAL_JWT_SECRET": "your-jwt-secret"
  }'
2

Create Secret for API Keys

aws secretsmanager create-secret \
  --name auditor-geo/prod/api-keys \
  --secret-string '{
    "GOOGLE_API_KEY": "your-google-key",
    "CSE_ID": "your-cse-id",
    "SERPER_API_KEY": "your-serper-key",
    "NVIDIA_API_KEY": "your-nvidia-key",
    "GOOGLE_PAGESPEED_API_KEY": "your-pagespeed-key"
  }'
3

Create Secret for Auth0

aws secretsmanager create-secret \
  --name auditor-geo/prod/auth0 \
  --secret-string '{
    "AUTH0_DOMAIN": "your-domain.auth0.com",
    "AUTH0_CLIENT_ID": "your-client-id",
    "AUTH0_CLIENT_SECRET": "your-client-secret",
    "AUTH0_API_AUDIENCE": "your-api-audience"
  }'

Container Deployment

Phase 6: ECR Repositories

1

Create Repositories

# Backend repository
aws ecr create-repository \
  --repository-name auditor-geo/backend \
  --image-scanning-configuration scanOnPush=true

# Frontend repository
aws ecr create-repository \
  --repository-name auditor-geo/frontend \
  --image-scanning-configuration scanOnPush=true
2

Configure Lifecycle Policies

aws ecr put-lifecycle-policy \
  --repository-name auditor-geo/backend \
  --lifecycle-policy-text '{
    "rules": [{
      "rulePriority": 1,
      "description": "Keep last 10 images",
      "selection": {
        "tagStatus": "any",
        "countType": "imageCountMoreThan",
        "countNumber": 10
      },
      "action": {"type": "expire"}
    }]
  }'

Phase 7: Build and Push Images

1

Login to ECR

aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  ACCOUNT.dkr.ecr.us-east-1.amazonaws.com
2

Build Backend Image

docker build -f Dockerfile.backend -t auditor-backend:latest .

docker tag auditor-backend:latest \
  ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/auditor-geo/backend:latest

docker push \
  ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/auditor-geo/backend:latest
3

Build Frontend Image

docker build -f Dockerfile.frontend \
  --build-arg NEXT_PUBLIC_API_URL=https://api.yourdomain.com \
  --build-arg NEXT_PUBLIC_BACKEND_URL=https://api.yourdomain.com \
  -t auditor-frontend:latest .

docker tag auditor-frontend:latest \
  ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/auditor-geo/frontend:latest

docker push \
  ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/auditor-geo/frontend:latest

Phase 8: ECS Cluster and Services

1

Create ECS Cluster

aws ecs create-cluster --cluster-name auditor-cluster
2

Create CloudWatch Log Groups

aws logs create-log-group --log-group-name /ecs/auditor-backend
aws logs create-log-group --log-group-name /ecs/auditor-frontend
aws logs create-log-group --log-group-name /ecs/auditor-worker
3

Create Task Execution Role

This role allows ECS to pull images and write logs:
aws iam create-role \
  --role-name ecsTaskExecutionRole \
  --assume-role-policy-document file://ecs-task-trust-policy.json

aws iam attach-role-policy \
  --role-name ecsTaskExecutionRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
4

Create Task Definition

Create a task definition JSON file with backend, frontend, and worker containers. Reference the Dockerfile configurations:
  • Backend: Port 8000, health check on /health/live
  • Frontend: Port 3000
  • Worker: Celery with concurrency=2
Register the task definition:
aws ecs register-task-definition --cli-input-json file://task-definition.json
5

Create ECS Service

aws ecs create-service \
  --cluster auditor-cluster \
  --service-name auditor-service \
  --task-definition auditor-task:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-private-1,subnet-private-2],securityGroups=[sg-ecs-xxxxx],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:...,containerName=backend,containerPort=8000"

Load Balancing and DNS

Phase 9: Application Load Balancer

1

Create ALB

aws elbv2 create-load-balancer \
  --name auditor-alb \
  --subnets subnet-public-1 subnet-public-2 \
  --security-groups sg-alb-xxxxx \
  --scheme internet-facing
2

Create Target Group

aws elbv2 create-target-group \
  --name auditor-backend-tg \
  --protocol HTTP \
  --port 8000 \
  --vpc-id vpc-xxxxx \
  --target-type ip \
  --health-check-enabled \
  --health-check-path /health \
  --health-check-interval-seconds 30 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3
3

Create HTTPS Listener

aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:... \
  --protocol HTTPS \
  --port 443 \
  --certificates CertificateArn=arn:aws:acm:... \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:...
4

Create HTTP Redirect Listener

aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:... \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=redirect,RedirectConfig="{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}"

Phase 10: CloudFront and Route 53

1

Create CloudFront Distribution

Create a distribution with:
  • Origin 1: ALB (for backend /api/* paths)
  • Default behavior: Forward all headers for API requests
  • Enable compression
  • Enable HTTP/2
  • Associate ACM certificate
2

Configure DNS

# Create A record pointing to CloudFront
aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch '{
    "Changes": [{
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "yourdomain.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z2FDTNDATAQYW2",
          "DNSName": "d123456789.cloudfront.net",
          "EvaluateTargetHealth": false
        }
      }
    }]
  }'

Security Hardening

Phase 11: WAF Configuration

1

Create Web ACL

aws wafv2 create-web-acl \
  --name auditor-waf \
  --scope CLOUDFRONT \
  --default-action Allow={} \
  --rules file://waf-rules.json
2

Add Managed Rule Sets

Include these AWS Managed Rules:
  • AWSManagedRulesCommonRuleSet
  • AWSManagedRulesKnownBadInputsRuleSet
  • AWSManagedRulesSQLiRuleSet
  • AWSManagedRulesAmazonIpReputationList
3

Add Rate Limiting

{
  "Name": "RateLimitRule",
  "Priority": 1,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 2000,
      "AggregateKeyType": "IP"
    }
  },
  "Action": {"Block": {}}
}
4

Associate with CloudFront

aws cloudfront update-distribution \
  --id E1234567890ABC \
  --web-acl-id arn:aws:wafv2:...

Monitoring and Auto-scaling

Phase 12: CloudWatch and Alarms

1

Create Dashboard

Create a CloudWatch dashboard tracking:
  • ECS CPU/Memory utilization
  • RDS CPU/Connections/Storage
  • Redis CPU/Memory/Evictions
  • ALB request count/error rate/latency
  • CloudFront requests/cache hit ratio
2

Configure Alarms

# ECS CPU alarm
aws cloudwatch put-metric-alarm \
  --alarm-name auditor-ecs-high-cpu \
  --alarm-description "ECS CPU > 80%" \
  --metric-name CPUUtilization \
  --namespace AWS/ECS \
  --statistic Average \
  --period 300 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 2

# RDS CPU alarm
aws cloudwatch put-metric-alarm \
  --alarm-name auditor-rds-high-cpu \
  --alarm-description "RDS CPU > 80%" \
  --metric-name CPUUtilization \
  --namespace AWS/RDS \
  --statistic Average \
  --period 300 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 2
3

Configure Auto-scaling

# Register scalable target
aws application-autoscaling register-scalable-target \
  --service-namespace ecs \
  --resource-id service/auditor-cluster/auditor-service \
  --scalable-dimension ecs:service:DesiredCount \
  --min-capacity 2 \
  --max-capacity 10

# Create scaling policy
aws application-autoscaling put-scaling-policy \
  --service-namespace ecs \
  --resource-id service/auditor-cluster/auditor-service \
  --scalable-dimension ecs:service:DesiredCount \
  --policy-name cpu-scaling-policy \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration file://scaling-policy.json

Production Environment Variables

Store all sensitive values in AWS Secrets Manager. Never hardcode credentials in task definitions.
# Database & Cache
DATABASE_URL=postgresql+psycopg2://auditor:PASSWORD@rds-endpoint:5432/auditor_db
DB_POOL_SIZE=10
DB_MAX_OVERFLOW=20
DB_POOL_TIMEOUT=30
DB_POOL_RECYCLE=3600
DB_POOL_PRE_PING=true
REDIS_URL=redis://redis-endpoint:6379/0
CELERY_BROKER_URL=redis://redis-endpoint:6379/0
CELERY_RESULT_BACKEND=redis://redis-endpoint:6379/1

# SSE Configuration
SSE_SOURCE=redis
SSE_FALLBACK_DB_INTERVAL_SECONDS=10
SSE_HEARTBEAT_SECONDS=30
SSE_RETRY_MS=5000

# Security
SECRET_KEY=<from-secrets-manager>
ENCRYPTION_KEY=<from-secrets-manager>
BACKEND_INTERNAL_JWT_SECRET=<from-secrets-manager>

# Environment
ENVIRONMENT=production
DEBUG=False

# CORS
CORS_ORIGINS=https://yourdomain.com
TRUSTED_HOSTS=yourdomain.com,api.yourdomain.com
FRONTEND_URL=https://yourdomain.com

# Auth0
AUTH0_DOMAIN=<from-secrets-manager>
AUTH0_ISSUER_BASE_URL=https://your-domain.auth0.com
AUTH0_API_AUDIENCE=<from-secrets-manager>
AUTH0_EXPECTED_CLIENT_ID=<from-secrets-manager>

# API Keys
GOOGLE_API_KEY=<from-secrets-manager>
SERPER_API_KEY=<from-secrets-manager>
NVIDIA_API_KEY=<from-secrets-manager>

# Monitoring
SENTRY_DSN=<your-sentry-dsn>

Disaster Recovery

Backup Strategy

1

RDS Automated Backups

  • Retention period: 30 days
  • Backup window: 03:00-04:00 UTC
  • Create manual snapshot before major changes
2

Test Recovery

Monthly recovery drill:
# Restore from snapshot
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier auditor-db-restored \
  --db-snapshot-identifier rds:auditor-db-2024-03-01-03-00
3

Document RTO/RPO

  • RTO (Recovery Time Objective): 1 hour
  • RPO (Recovery Point Objective): 5 minutes (via automated backups)

Cost Optimization

Estimated monthly costs for production deployment:
  • ECS Fargate: $50-100
  • RDS db.t3.small: $50-70
  • ElastiCache cache.t3.small: $40-60
  • ALB: $20-30
  • CloudFront: $10-30
  • Data Transfer: $20-40
  • Total: ~$300/month

Optimization Tips

  • Use AWS Free Tier for eligible services
  • Configure billing alerts at 100,100, 200, $300
  • Enable Cost Explorer for detailed analysis
  • Use Savings Plans for predictable workloads
  • Right-size instances based on CloudWatch metrics
  • Configure ECS auto-scaling to scale down during off-peak hours

Next Steps

Docker Deployment

Run LatentGEO locally with Docker Compose

CI/CD Setup

Automate deployments with GitHub Actions

Build docs developers (and LLMs) love