April 2026
·3 min read
Deploy a Full-Stack App on AWS: ECS, RDS & CloudFront
Deploy a containerised full-stack app on AWS using ECS Fargate for compute, RDS for the database, and CloudFront as a CDN — infrastructure you can actually maintain.
Introduction
- The goal: a production-grade AWS architecture for a full-stack app (e.g., Next.js frontend + Laravel API).
- Components: ECS Fargate (containers), ECR (image registry), RDS PostgreSQL (database), CloudFront (CDN), ALB (load balancer), ACM (SSL), Route 53 (DNS).
- Prerequisite: Docker images built and working locally; AWS CLI configured.
Architecture Overview
Browser → Route 53 → CloudFront → ALB → ECS Fargate Tasks → RDS PostgreSQL
↑
(static assets cached at CloudFront edge)- CloudFront caches static assets and forwards API requests to ALB.
- ECS Fargate runs containerised app tasks — no EC2 instances to manage.
- RDS in a private subnet — only reachable from ECS tasks via security groups.
Step 1: Push Docker Images to ECR
# Create ECR repositories
aws ecr create-repository --repository-name my-app/frontend
aws ecr create-repository --repository-name my-app/api
# Authenticate and push
aws ecr get-login-password --region ap-southeast-1 | \
docker login --username AWS --password-stdin <account>.dkr.ecr.ap-southeast-1.amazonaws.com
docker build -t my-app/frontend .
docker tag my-app/frontend:latest <account>.dkr.ecr.ap-southeast-1.amazonaws.com/my-app/frontend:latest
docker push <account>.dkr.ecr.ap-southeast-1.amazonaws.com/my-app/frontend:latestStep 2: VPC and Networking
- Use a VPC with public subnets (ALB, NAT Gateway) and private subnets (ECS tasks, RDS).
- Security groups:
- ALB SG: inbound 443 from 0.0.0.0/0.
- ECS SG: inbound from ALB SG only.
- RDS SG: inbound 5432 from ECS SG only.
- Use Terraform or AWS CloudFormation to manage networking as code (see Terraform article).
Step 3: RDS PostgreSQL Setup
- Engine: PostgreSQL 16, instance
db.t4g.micro(free-tier eligible). - Multi-AZ: enabled for production (automatic failover).
- Subnet group: private subnets.
- Store credentials in AWS Secrets Manager; inject into ECS task as environment variables.
Step 4: ECS Cluster and Task Definitions
- Create an ECS cluster (Fargate launch type).
- Task definition: specify CPU (256–1024), memory, container image URI, port mappings, environment variables, and log driver (
awslogs).
{
"containerDefinitions": [{
"name": "frontend",
"image": "<account>.dkr.ecr.ap-southeast-1.amazonaws.com/my-app/frontend:latest",
"portMappings": [{ "containerPort": 3000 }],
"environment": [
{ "name": "NEXT_PUBLIC_API_URL", "value": "https://api.myapp.com" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": { "awslogs-group": "/ecs/frontend", "awslogs-region": "ap-southeast-1" }
}
}]
}Step 5: Application Load Balancer
- Create an ALB in public subnets with HTTPS listener (ACM certificate from
*.myapp.com). - Target group:
iptarget type pointing to ECS tasks. - Health check:
GET /api/health→ expect 200. - Redirect HTTP → HTTPS at the listener level.
Step 6: ECS Service
- Create an ECS Service that maintains desired count (2+ tasks for HA) using the task definition.
- Rolling deployment:
minimumHealthyPercent: 100,maximumPercent: 200— zero-downtime deploys. - Auto Scaling: scale out when CPU > 70%, scale in when CPU < 30%.
Step 7: CloudFront Distribution
- Origin 1: ALB — for dynamic requests (
/api/*, server-rendered pages). - Origin 2: S3 — for static assets (if using S3 for uploads).
- Cache behaviour: cache static assets (
/_next/static/*) aggressively (TTL 31536000); forward all headers for dynamic paths. - SSL: ACM certificate, redirect HTTP to HTTPS.
- Custom domain: CNAME record in Route 53 pointing to CloudFront.
Step 8: CI/CD Integration
- On merge to
main: build Docker image → push to ECR → trigger ECS rolling deploy. - GitHub Actions step:
aws ecs update-service --force-new-deployment. - Use OIDC for GitHub → AWS IAM role (no long-lived access keys).
Cost Estimates (ap-southeast-1)
| Service | Config | Monthly estimate |
|---|---|---|
| ECS Fargate | 2 tasks × 0.25 vCPU / 0.5 GB | ~$15 |
RDS db.t4g.micro | Single-AZ | ~$15 |
| ALB | 1 LCU estimate | ~$20 |
| CloudFront | 10 GB transfer | ~$1 |
| NAT Gateway | Low traffic | ~$35 |
Conclusion
- ECS Fargate + RDS + CloudFront is the modern AWS baseline for containerised apps — no EC2 patching, no Kubernetes overhead.
- NAT Gateway cost is usually the surprise; consider VPC endpoints for ECR/Secrets Manager to reduce NAT traffic.
- Next steps: add AWS WAF in front of CloudFront, enable RDS Multi-AZ, and codify everything in Terraform.