Deployment Architecture
This page describes the AWS infrastructure used for the Infracast SaaS platform and the recommended architecture for self-hosted Terraform deployments.
AWS Architecture Overview
graph TB
Internet((Internet))
subgraph AWS
CF[CloudFront<br/>UI CDN]
S3UI[S3 Bucket<br/>React SPA]
ACM[ACM Certificate<br/>*.infracast.io]
subgraph VPC["VPC (10.0.0.0/16)"]
subgraph Public["Public Subnets (us-east-1a/b)"]
ALB[Application Load Balancer<br/>HTTPS :443]
NAT1[NAT Gateway<br/>AZ-a]
NAT2[NAT Gateway<br/>AZ-b]
end
subgraph Private["Private Subnets (us-east-1a/b)"]
ECS1[ECS Fargate Task<br/>vulcan-api]
ECS2[ECS Fargate Task<br/>vulcan-api replica]
end
subgraph Data["Data Subnets (us-east-1a/b)"]
RDS_P[(RDS Primary<br/>PostgreSQL 15)]
RDS_S[(RDS Standby<br/>Multi-AZ)]
end
end
S3Art[S3 Bucket<br/>Evidence Artifacts]
CW[CloudWatch<br/>Logs & Metrics]
SM[Secrets Manager<br/>DB credentials, JWT secret]
end
Internet -->|HTTPS| CF
CF --> S3UI
Internet -->|HTTPS :443| ALB
ACM --> ALB
ALB --> ECS1
ALB --> ECS2
ECS1 --> NAT1
ECS2 --> NAT2
ECS1 --> RDS_P
ECS2 --> RDS_P
RDS_P -.->|sync| RDS_S
ECS1 --> S3Art
ECS1 --> SM
ECS1 --> CW
Network Architecture
The Terraform module creates a three-tier VPC:
| Tier | Subnets | Resources | Internet Access |
|---|---|---|---|
| Public | 2x (one per AZ) | ALB, NAT Gateways, EIPs | Direct via IGW |
| Private | 2x (one per AZ) | ECS Fargate tasks | Outbound via NAT |
| Data | 2x (one per AZ) | RDS PostgreSQL | None |
Security Groups
| Resource | Inbound | Outbound |
|---|---|---|
| ALB | 443 from 0.0.0.0/0 | 8080 to ECS SG |
| ECS tasks | 8080 from ALB SG | 5432 to RDS SG, 443 to 0.0.0.0/0 (NAT) |
| RDS | 5432 from ECS SG | None |
Application Load Balancer
The ALB terminates TLS and routes all traffic:
- HTTPS listener (443) — routes to ECS target group
- HTTP listener (80) — redirects to HTTPS
- Health check —
GET /healthzon port 8080, expects 200 - Stickiness — disabled (stateless API)
- Connection draining — 30 seconds (graceful shutdown)
ECS Fargate
The Infracast API runs as ECS Fargate tasks:
| Parameter | Development | Production |
|---|---|---|
| CPU | 512 (0.5 vCPU) | 1024 (1 vCPU) |
| Memory | 1024 MB | 2048 MB |
| Desired count | 1 | 2 |
| Min count | 1 | 2 |
| Max count | 3 | 10 |
| Auto-scale target | 80% CPU | 70% CPU |
Task Definition
Key environment variables injected into ECS tasks:
{
"environment": [
{"name": "DATABASE_URL", "valueFrom": "arn:aws:secretsmanager:...:db-url"},
{"name": "JWT_SECRET", "valueFrom": "arn:aws:secretsmanager:...:jwt-secret"},
{"name": "API_PORT", "value": "8080"},
{"name": "PLUGIN_DIR", "value": "/usr/local/lib/infracast/plugins"},
{"name": "CONTENT_CDN_URL", "value": "https://<your-cdn>.cloudfront.net"}
]
}
All secrets are sourced from AWS Secrets Manager — never hardcoded in task definitions.
Rolling Deployments
ECS performs zero-downtime rolling deployments:
- New task started with updated image
- Health check passes (
/healthzreturns 200) - Task registered with ALB target group
- Old task deregistered and drained (30s)
- Old task stopped
RDS PostgreSQL
| Parameter | Development | Production |
|---|---|---|
| Engine | PostgreSQL 15 | PostgreSQL 15 |
| Instance class | db.t3.medium | db.r5.large |
| Multi-AZ | No | Yes |
| Storage | 20 GB gp3 | 100 GB gp3, autoscale |
| Encryption | AES-256 (KMS) | AES-256 (CMK) |
| Backup retention | 7 days | 35 days |
| Automated backups | Yes | Yes |
| Performance Insights | No | Yes |
Database Migrations
Infracast runs database migrations automatically on startup using an embedded migration runner. No manual intervention is needed for upgrades.
In production, ensure only one ECS task runs migrations at startup by using the leader election lock or setting desired_count = 1 during initial deployment, then scaling up after the first task is healthy.
CloudFront (UI Distribution)
The React SPA is served from CloudFront backed by S3:
- Origin: S3 bucket with OAC (Origin Access Control)
- Caching: Static assets cached at edge (1 year TTL with content hashing)
- HTTPS: Enforced, HTTP redirected to HTTPS
- Custom domain:
app.infracast.io(SaaS) or your domain
Static assets use content-hash filenames (e.g., main.abc123.js). The index.html has a 0-second TTL and is always fetched fresh. If the UI appears stale after an upgrade, hard-refresh (Ctrl+Shift+R / Cmd+Shift+R) or invalidate the CloudFront distribution.
DNS and SSL/TLS
SaaS
| Record | Type | Value |
|---|---|---|
app.infracast.io | CNAME | CloudFront distribution |
api.infracast.io | CNAME | ALB DNS name |
Self-Hosted
- Create an ACM certificate for your domain (e.g.,
infracast.example.com) - Pass the ARN to the Terraform module:
certificate_arn = "arn:aws:acm:..." - Create a CNAME from your domain to the ALB DNS name (output by Terraform)
- DNS propagation typically takes 5–60 minutes
Dev vs Prod Environments
Infracast maintains separate AWS accounts for dev and prod:
| Account | ID | Purpose |
|---|---|---|
vulcan-dev | (internal) | Development, staging, testing |
vulcan-prod | (internal) | Production SaaS |
Key differences:
| Setting | Dev | Prod |
|---|---|---|
| RDS Multi-AZ | No | Yes |
| NAT Gateways | 1 | 2 |
| ECS task count | 1 | 2 |
| Backup retention | 7 days | 35 days |
| Deletion protection | No | Yes |
Terraform Module Reference
module "vulcan" {
source = "github.com/azgardtek/vulcan//terraform/modules/vulcan-ecs"
environment = "prod"
vpc_cidr = "10.0.0.0/16"
# ECS
ecs_cpu = 1024
ecs_memory = 2048
ecs_desired_count = 2
# RDS
db_instance_class = "db.r5.large"
db_multi_az = true
db_storage_gb = 100
# Domain
domain_name = "infracast.example.com"
certificate_arn = "arn:aws:acm:us-east-1:123456789:certificate/abc"
tags = {
Project = "Infracast"
Environment = "prod"
}
}
See Deployment Guide for the full deployment walkthrough.