Infrastructure
All SA3 infrastructure runs on AWS eu-west-3 (Paris) and is provisioned via Terraform at infrastructure/terraform/sa3/.
AWS Resources
Terraform Files
| File | Resources |
|---|---|
main.tf | Provider config, Terraform backend |
vpc.tf | VPC, subnets (public + private), NAT Gateway, Internet Gateway, route tables |
rds.tf | RDS PostgreSQL 15 instance, subnet group, parameter group, security group |
s3.tf | S3 bucket (sa3-files), versioning, lifecycle rules, CORS |
cloudfront.tf | CloudFront distribution for S3 origin (public assets, signed PDFs) |
apprunner.tf | App Runner service, VPC connector, auto-scaling config, custom domain |
lambda.tf | PDF Lambda function, SQS event source mapping (batch_size=1), DLQ, reserved concurrency |
iam.tf | IAM roles for App Runner, Lambda; S3, RDS, SQS, KMS, SES permissions |
kms.tf | KMS keys: sa3-general-encryption-key, sa3-student-pii-key |
secrets.tf | Secrets Manager for database credentials |
ses.tf | SES domain identity, DKIM |
cloudwatch.tf | CloudWatch log groups, alarms |
variables.tf | Input variables |
outputs.tf | Output values (App Runner URL, RDS endpoint, S3 bucket name) |
Key Configuration
| Resource | Value |
|---|---|
| RDS instance | db.t3.small (~$0.034/hr) |
| RDS storage | 20 GB gp3, encrypted via sa3-general-encryption-key |
| RDS backups | 7-day retention, automated snapshots |
| App Runner | 1 vCPU / 2 GB, ECR source |
| App Runner scaling | Min 1 instance, max 4 |
| Lambda memory | 512 MB |
| Lambda timeout | 300 seconds |
| Lambda concurrency | 10 reserved |
| SQS visibility timeout | 360 seconds |
| SQS DLQ | max receive count = 3 |
| CloudFront | S3 origin only (not App Runner) |
| KMS PII key | Restricted to App Runner + Lambda roles |
Hard Constraints
HOSTNAME=0.0.0.0must be set in App Runner environment variables and Dockerfile. Without it, Next.js binds to localhost and health probes fail.- CloudFront on S3 only. Do not add App Runner as a CloudFront origin -- it breaks Next.js 15 streaming and RSC
Varyheader routing. - SQS visibility timeout > Lambda timeout. The 360s visibility timeout must exceed the 300s Lambda timeout to prevent duplicate processing.