Student Assignment | Docker, AWS, Terraform
A full-stack student registration app (Flask + Express) deployed across three configurations on AWS.
docker-assignment/
βββ backend/ # Flask REST API (Python)
β βββ app.py
β βββ requirements.txt
β βββ Dockerfile
βββ frontend/ # Express frontend (Node.js + EJS)
β βββ server.js
β βββ package.json
β βββ views/
β βββ Dockerfile
βββ docker-compose.yml # Local development
βββ terraform/
βββ backend-bootstrap/ # S3 + DynamoDB state backend (run first)
β βββ main.tf
βββ part1-single-ec2/ # Part 1 β single EC2
β βββ main.tf
β βββ variables.tf
β βββ outputs.tf
β βββ user_data.sh
βββ part2-separate-ec2/ # Part 2 β two EC2 instances
β βββ main.tf
β βββ variables.tf
β βββ outputs.tf
β βββ flask_userdata.sh
β βββ express_userdata.sh
βββ part3-ecs-fargate/ # Part 3 β ECS Fargate + ECR + ALB
βββ main.tf
βββ variables.tf
βββ outputs.tf
βββ push-to-ecr.sh
| Tool | Version | Install |
|---|---|---|
| AWS CLI | v2+ | aws.amazon.com/cli |
| Terraform | v1.3+ | terraform.io |
| Docker | 24+ | docker.com |
| Git | any | git-scm.com |
AWS Configuration:
aws configure
# Enter: Access Key ID, Secret Access Key, Region (us-east-1), Output (json)Create EC2 Key Pair (for Parts 1 & 2):
aws ec2 create-key-pair \
--key-name docker-assignment-key \
--query 'KeyMaterial' \
--output text > docker-assignment-key.pem
chmod 400 docker-assignment-key.pemBefore deploying any part, create the shared S3 + DynamoDB backend:
cd terraform/backend-bootstrap
terraform init
terraform plan
terraform apply -auto-approveWhat this creates:
- S3 bucket:
docker-assignment-tfstate(versioned + encrypted) - DynamoDB table:
terraform-lock(state locking)
Internet
β
βΌ
EC2 (Ubuntu 22.04 β t2.micro)
βββ Flask Backend :5000 (PM2 process)
βββ Express Frontend :3000 (PM2 process)
- Security Group (ports 22, 3000, 5000)
- EC2 Instance with Cloud-Init user data
- PM2 process manager (auto-restart on crash/reboot)
cd terraform/part1-single-ec2
terraform init
terraform plan
terraform applyinstance_public_ip = "54.x.x.x"
flask_url = "http://54.x.x.x:5000"
express_url = "http://54.x.x.x:3000"
ssh_command = "ssh -i docker-assignment-key.pem ubuntu@54.x.x.x"
# Health check Flask
curl http://54.x.x.x:5000/health
# β {"status":"ok"}
# Open Express frontend in browser
open http://54.x.x.x:3000
# SSH and check PM2 processes
ssh -i docker-assignment-key.pem ubuntu@54.x.x.x
pm2 list
pm2 logsterraform destroy -auto-approveInternet
β
ββββΊ Express EC2 (Subnet B) :3000
β β FLASK_URL=http://<private-ip>:5000
β βΌ (internal VPC traffic)
ββββΊ Flask EC2 (Subnet A) :5000
- VPC (
10.0.0.0/16) with 2 public subnets across 2 AZs - Internet Gateway + Route Tables
- 2 Security Groups (Flask SG allows 5000 from Express SG)
- 2 EC2 Instances (Flask in subnet-a, Express in subnet-b)
- Systemd services for auto-restart
cd terraform/part2-separate-ec2
terraform init
terraform plan
terraform applyflask_public_ip = "54.x.x.x"
express_public_ip = "3.y.y.y"
flask_url = "http://54.x.x.x:5000"
express_url = "http://3.y.y.y:3000"
flask_health_check = "http://54.x.x.x:5000/health"
# Flask health check (public)
curl http://54.x.x.x:5000/health
# Express frontend (reaches Flask via private IP internally)
open http://3.y.y.y:3000
# Check Flask systemd service
ssh -i docker-assignment-key.pem ubuntu@54.x.x.x
sudo systemctl status flask-backend
sudo journalctl -u flask-backend -n 50
# Check Express systemd service
ssh -i docker-assignment-key.pem ubuntu@3.y.y.y
sudo systemctl status express-frontend
sudo journalctl -u express-frontend -n 50Internet βββΊ express_sg (3000/tcp) βββΊ Express EC2
Express EC2 βββΊ flask_sg (5000/tcp, source: express_sg) βββΊ Flask EC2
terraform destroy -auto-approveInternet
β
βΌ port 80
Application Load Balancer (ALB)
β
βββββββ /api/* βββββββΊ ECS Service: Flask (Fargate) :5000
β (ECR: docker-assignment-flask-backend)
β
βββββββ /* βββββββββββΊ ECS Service: Express (Fargate) :3000
(ECR: docker-assignment-express-frontend)
| Resource | Count | Detail |
|---|---|---|
| ECR Repositories | 2 | flask-backend, express-frontend |
| VPC | 1 | 10.0.0.0/16 |
| Public Subnets | 2 | Multi-AZ |
| Internet Gateway | 1 | |
| Security Groups | 3 | ALB, flask-sg, express-sg |
| IAM Role | 1 | ECS Task Execution |
| CloudWatch Log Groups | 2 | 7-day retention |
| ECS Cluster | 1 | Container Insights enabled |
| ECS Task Definitions | 2 | |
| ECS Services | 2 | Fargate launch type |
| ALB | 1 | |
| Target Groups | 2 | |
| ALB Listener Rules | 2 | Path-based routing |
cd terraform/part3-ecs-fargate
terraform init
terraform plan
terraform apply# From project root (where backend/ and frontend/ folders live)
chmod +x terraform/part3-ecs-fargate/push-to-ecr.sh
./terraform/part3-ecs-fargate/push-to-ecr.sh us-east-1What this does:
- Authenticates Docker to ECR
- Builds
backend/Dockerfileβ pushes to ECR flask repo - Builds
frontend/Dockerfileβ pushes to ECR express repo
aws ecs update-service \
--cluster docker-assignment-cluster \
--service docker-assignment-flask-service \
--force-new-deployment \
--region us-east-1
aws ecs update-service \
--cluster docker-assignment-cluster \
--service docker-assignment-express-service \
--force-new-deployment \
--region us-east-1ecr_flask_url = "123456789.dkr.ecr.us-east-1.amazonaws.com/docker-assignment-flask-backend"
ecr_express_url = "123456789.dkr.ecr.us-east-1.amazonaws.com/docker-assignment-express-frontend"
alb_dns_name = "docker-assignment-alb-123456.us-east-1.elb.amazonaws.com"
frontend_url = "http://docker-assignment-alb-123456.us-east-1.elb.amazonaws.com"
flask_api_url = "http://docker-assignment-alb-123456.us-east-1.elb.amazonaws.com/api"
flask_health_url = "http://docker-assignment-alb-123456.us-east-1.elb.amazonaws.com/health"
# Flask health check (via ALB)
curl http://<alb-dns-name>/health
# β {"status":"ok"}
# Flask submit endpoint
curl -X POST http://<alb-dns-name>/submit \
-H "Content-Type: application/json" \
-d '{"name":"John","student_id":"S001","email":"j@uni.edu","course":"CS","grade":"A"}'
# Express frontend
open http://<alb-dns-name>
# Check ECS task status
aws ecs describe-services \
--cluster docker-assignment-cluster \
--services docker-assignment-flask-service docker-assignment-express-service \
--region us-east-1
# View CloudWatch logs
aws logs tail /ecs/docker-assignment/flask-backend --follow
aws logs tail /ecs/docker-assignment/express-frontend --followterraform destroy -auto-approveRun both apps locally with Docker Compose:
# Build and start
docker compose up --build
# Access
# Express frontend: http://localhost:3000
# Flask backend: http://localhost:5000/health
# Stop
docker compose down| Requirement | Status |
|---|---|
variables.tf in all parts |
β |
outputs.tf in all parts |
β |
| S3 remote state backend | β |
| DynamoDB state locking | β |
terraform plan before apply |
β |
| Security groups with least privilege | β |
| User data / Cloud-Init automation | β |
| ECR repositories for Docker images | β |
| ECS Fargate cluster + services | β |
| ALB with path-based routing | β |
| CloudWatch logs for ECS | β |
| Resource tagging | β |
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Health check β {"status":"ok"} |
POST |
/submit |
Submit student registration |
Sample POST /submit:
{
"name": "John Doe",
"student_id": "S12345",
"email": "john@uni.edu",
"course": "Computer Science",
"grade": "A"
}Response:
{
"success": true,
"message": "Student 'John Doe' submitted successfully!",
"data": {
"name": "John Doe",
"student_id": "S12345",
"email": "john@uni.edu",
"course": "Computer Science",
"grade": "A",
"grade_status": "Excellent"
}
}Mohammad Noman Qadri
Docker + AWS + Terraform Assignment
GitHub: nomanqadri34/docker-assignment