Home / AWS

AWSHow-toIntermediate

Transformation: move an EC2-hosted application to containers on ECS Fargate

Replatform a web application from EC2 instances to Amazon ECS on Fargate. Containerise, push to ECR, define tasks with secrets and IAM roles, run behind an ALB with autoscaling and shift traffic with weighted target groups.

Deepak Bhardwaj2 min read

Applies toAmazon ECS, AWS Fargate, Amazon ECR, Application Load Balancer, AWS CLI v2

Why Fargate

No instances to patch or scale, per-task IAM roles, and deployments that replace tasks instead of mutating servers. ECS is simpler than Kubernetes for teams that just need to run services.

1. Containerise

Make the app stateless: config from environment variables, logs to stdout, uploads to S3, sessions in ElastiCache or DynamoDB.

FROM public.ecr.aws/docker/library/node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]

2. Push to ECR

aws ecr create-repository --repository-name web --image-scanning-configuration scanOnPush=true --image-tag-mutability IMMUTABLE
aws ecr get-login-password | docker login --username AWS --password-stdin 111122223333.dkr.ecr.eu-west-1.amazonaws.com
docker build -t 111122223333.dkr.ecr.eu-west-1.amazonaws.com/web:1.0.0 .
docker push 111122223333.dkr.ecr.eu-west-1.amazonaws.com/web:1.0.0

3. Task definition

{
  "family": "web",
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "512", "memory": "1024",
  "executionRoleArn": "arn:aws:iam::111122223333:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::111122223333:role/web-task-role",
  "containerDefinitions": [{
    "name": "web",
    "image": "111122223333.dkr.ecr.eu-west-1.amazonaws.com/web:1.0.0",
    "portMappings": [{ "containerPort": 3000 }],
    "secrets": [{ "name": "DB_PASSWORD", "valueFrom": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:web/db-AbCdEf" }],
    "logConfiguration": { "logDriver": "awslogs", "options": {
      "awslogs-group": "/ecs/web", "awslogs-region": "eu-west-1", "awslogs-stream-prefix": "web" } },
    "healthCheck": { "command": ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"], "interval": 15, "retries": 3 }
  }]
}

The execution role pulls images and reads secrets; the task role is what your code uses for AWS APIs.

4. Service behind the ALB

aws ecs register-task-definition --cli-input-json file://web-task.json
aws ecs create-service --cluster prod --service-name web --task-definition web \
  --desired-count 3 --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-a,subnet-b,subnet-c],securityGroups=[sg-web],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=<tg-fargate-arn>,containerName=web,containerPort=3000" \
  --deployment-configuration "deploymentCircuitBreaker={enable=true,rollback=true}"

The target group must use target type ip. Private subnets need a NAT gateway or VPC endpoints (ECR, S3, Logs, Secrets Manager) to pull images.

5. Autoscaling

aws application-autoscaling register-scalable-target --service-namespace ecs \
  --resource-id service/prod/web --scalable-dimension ecs:service:DesiredCount --min-capacity 3 --max-capacity 20
aws application-autoscaling put-scaling-policy --service-namespace ecs --resource-id service/prod/web \
  --scalable-dimension ecs:service:DesiredCount --policy-name cpu60 --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration '{"TargetValue":60,"PredefinedMetricSpecification":{"PredefinedMetricType":"ECSServiceAverageCPUUtilization"}}'

6. Shift traffic from EC2

Use a weighted forward action on the ALB listener with the old (EC2) and new (Fargate) target groups:

aws elbv2 modify-listener --listener-arn <arn> --default-actions \
  '[{"Type":"forward","ForwardConfig":{"TargetGroups":[{"TargetGroupArn":"<tg-ec2>","Weight":90},{"TargetGroupArn":"<tg-fargate>","Weight":10}]}}]'

Move to 50/50 and then 0/100 while watching 5xx and latency. Keep the EC2 ASG for a rollback window, then delete it.

Checklist

  • App stateless, config by environment, secrets in Secrets Manager
  • Image scanned in ECR with immutable tags
  • Separate execution and task roles, least privilege
  • Deployment circuit breaker with rollback
  • Weighted traffic shift completed; EC2 retired