Deploying Angular + FastAPI on AWS Fargate: From Zero to Production
Deploying Angular + FastAPI on AWS Fargate: From Zero to Production focuses on what actually matters in practice: decision context, safe rollout steps, and verification points.
Deploying an Angular 19 + FastAPI App on AWS Fargate: From Zero to Production
Consolidated from real engineering sessions spanning April–May 2026. All secrets, account IDs, and internal endpoints replaced with placeholders.
Delivery Focus 1: Where this architecture earns its value for predictable operations (Deploying Angular Fastapi)
This article documents the complete journey of deploying a full-stack web application (Angular 19 SSR frontend + FastAPI backend + nginx reverse proxy) on AWS ECS Fargate — from initial CloudFormation stack creation through SSL/domain setup, autoscaling, and production hardening.
Editorial review note for Deploying Angular Fastapi
This section was reviewed by a human editor to keep the recommendations actionable and technically grounded. Reviewed by: Med Amine Mahmoud. Last editorial review: 2026-05-26T16:10:01Z.
Delivery Focus 3: How to avoid expensive rework for cleaner ownership (Deploying Angular Fastapi)
Steps
- Register domain and create Route 53 hosted zone
- Request ACM certificate (DNS validation via Route 53 CNAME records)
- Create Route 53 A-record aliases pointing
example.comandwww.example.comto ALB - Add HTTPS listener on ALB port 443 with TLS 1.3 security policy
- Change HTTP listener from forward to 301 redirect to HTTPS
- Open port 443 in ALB security group
- Update CORS origins in backend from
*to specific HTTPS domains
# Add HTTPS listener
aws elbv2 create-listener `
--load-balancer-arn $ALB_ARN `
--protocol HTTPS --port 443 `
--ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 `
--certificates CertificateArn=$ACM_CERT_ARN `
--default-actions Type=forward,TargetGroupArn=$TG_ARN
# Modify HTTP to redirect
aws elbv2 modify-listener `
--listener-arn $HTTP_LISTENER_ARN `
--default-actions "Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}"
Result
https://www.example.com— fully functional with SSR, security headers, gziphttp://example.com— 301 redirect to HTTPS- All security headers present (HSTS, CSP, X-Frame-Options, etc.)
Delivery Focus 4: Where teams usually get this wrong for measurable outcomes (Deploying Angular Fastapi)
Before (Score: 1/10)
- Single task, single AZ, no auto-scaling
- No deployment circuit breaker
- No per-container resource limits
- Estimated capacity: ~30 concurrent users before degradation
After (Score: 9/10)
# Auto Scaling configuration
ScalableTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MinCapacity: 1
MaxCapacity: 8
ResourceId: !Sub 'service/${ECSCluster}/${ECSService}'
ScalableDimension: ecs:service:DesiredCount
CPUScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyType: TargetTrackingScaling
TargetTrackingScalingPolicyConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
TargetValue: 60.0
RequestCountScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyType: TargetTrackingScaling
TargetTrackingScalingPolicyConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ALBRequestCountPerTarget
TargetValue: 1000.0
Key Changes
- Task size: Upgraded to 2 vCPU / 4 GB (later right-sized back to 1 vCPU / 2 GB)
- Auto Scaling: min=1, max=8, CPU target 60%, request target 1000
- Circuit Breaker: Enabled with auto rollback
- Container Insights: Enabled for per-container metrics
Delivery Focus 5: The practical decision path for fewer incident surprises (Deploying Angular Fastapi)
Architecture Decision: Single ALB with Host-Based Routing
Instead of a separate ALB for dev ($16/month), we consolidated:
Single ALB
├── Rule: Host=www.example.com → prod target group
├── Rule: Host=dev.example.com + SourceIP=<YOUR_IP>/32 → dev target group
└── Default: 403 Forbidden (blocks direct ALB access)
Monthly savings: ~$16/month by eliminating the second ALB.
Dev Auto-Shutdown (Save ~$18/month idle costs)
Lambda-based auto-shutdown after 30 minutes of zero traffic:
CloudWatch Alarm (RequestCount = 0 for 30 min)
→ SNS Topic
→ Lambda (scales ECS service to 0)
→ Email notification
Delivery Focus 6: How to execute without guesswork for this workload (Deploying Angular Fastapi)
ALB Direct Access Blocking
- Default action on both HTTP/HTTPS listeners returns 403
- Only host-header matching rules forward traffic
- Result: ALB DNS name returns 403, domain names return 200
CSP Header Gotcha
Adding upgrade-insecure-requests to Content-Security-Policy caused a complete blank page when serving over HTTP. The directive tells browsers to upgrade all subresource loads to HTTPS, but since the ALB initially served HTTP only, all JS/CSS assets failed to load.
Lesson: Never add upgrade-insecure-requests to CSP until HTTPS is fully configured end-to-end.
Database Migration Strategy
ECS Fargate with SQLAlchemy requires careful migration handling:
create_tables()only creates new tables, doesn't alter existing ones- Column additions need explicit
ALTER TABLEin a migration function - The app's
_migrate_columns()function handles schema drift on startup
Delivery Focus 7: What to validate before shipping for your runbook (Deploying Angular Fastapi)
| Lesson | Context |
|---|---|
| Deploy with DesiredCount=0 first | Creates infra without needing images ready |
| Angular SSR needs ALLOWED_HOSTS=* in ECS | ALB health checks use private IPs |
| CSP upgrade-insecure-requests breaks HTTP | Only add after HTTPS is fully configured |
| Single ALB with host routing saves $16/mo | Consolidate dev/prod behind one ALB |
| Auto-shutdown Lambda saves idle costs | 30-min idle → scale to 0 |
| Circuit breaker prevents bad deploy loops | Enable DeploymentCircuitBreaker |
| Container Insights needed for debugging | No per-container metrics without it |
Delivery Focus 8: Tradeoffs that matter in production for production readiness (Deploying Angular Fastapi)
| Resource | Monthly Cost | % of Total |
|---|---|---|
| ECS Fargate (1 vCPU / 2 GB) | ~$36 | 43% |
| ALB + Public IPs | ~$27 | 32% |
| RDS (db.t3.micro) | ~$15 | 18% |
| Other (Secrets, Logs, ECR) | ~$6 | 7% |
| Total | ~$84/month |
With optimizations (Spot, ARM64, reserved): ~$70-85/month projected.
Delivery Focus 9: Implementation details that change outcomes for sustained reliability (Deploying Angular Fastapi)
Architecture
Internet → ALB (port 80/443) → ECS Fargate Task
├── nginx (reverse proxy)
├── frontend (Angular SSR, port 4000)
└── backend (FastAPI, port 8000)
→ RDS PostgreSQL (db.t3.micro)
CloudFormation Strategy
The initial deployment used a zero-count deploy strategy:
- Deploy CloudFormation with
DesiredCount: 0— creates all infrastructure without needing container images - Build Docker images locally while stack creates
- Push images to ECR once repos are available
- Update service to
DesiredCount: 1
# Key CloudFormation resources
Resources:
ECSCluster:
Type: AWS::ECS::Cluster
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Cpu: '1024'
Memory: '2048'
NetworkMode: awsvpc
RequiresCompatibilities: [FARGATE]
ContainerDefinitions:
- Name: nginx
Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/myapp-nginx:latest'
PortMappings: [{ContainerPort: 80}]
Essential: true
- Name: frontend
Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/myapp-frontend:latest'
Essential: true
- Name: backend
Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/myapp-backend:latest'
Essential: true
Critical Issue: Angular SSR Host Rejection
After deployment, the ALB health checks returned 500 errors. Root cause: Angular SSR's CommonEngine was rejecting requests from the ALB's private IP because the Host header didn't match allowed hosts.
Fix: Add ALLOWED_HOSTS=* environment variable to disable host checking in the ECS task definition:
// server.ts — handle ALLOWED_HOSTS
const allowedHosts = process.env['ALLOWED_HOSTS'];
if (allowedHosts === '*') {
// Skip host validation for ALB health checks
}
