In this article you will understand How to Deploy Docker App on AWS ECS 2025
What is AWS ECS and Why Use It
Imagine you have a big apartment building (AWS cloud), and you want to run many different applications (like a pizza delivery app, a photo sharing app, etc.).
AWS ECS (Elastic Container Service) is like a smart apartment manager that:
- π¦ Takes your apps (packed in containers like shipping boxes)
- π Finds the right apartment (server) for each app
- β‘ Makes sure they have enough power and space
- π Replaces them automatically if they break
- π Adds more copies when busy, removes when quiet
Why Use ECS
β Itβs Gotten Much Easier
2020: Complex setup, lots of manual work 2025: Point-and-click setup, auto-everything!
β Cost-Effective
- Only pay for what you use (like Uber vs owning a car)
- Auto-scaling saves money during quiet times
β Super Reliable
- If one container crashes β ECS starts a new one instantly
- Built-in health checks and self-healing
β Perfect for Modern Apps
- Microservices architecture
- Easy updates and rollbacks
- Multiple environments (dev, staging, prod)
When to Use ECS vs EKS vs EC2?
Think of them as different types of transportation:
π EC2 = Buying Your Own Car
Use when:
- You want FULL control over everything
- Running legacy applications that need specific setup
- You have dedicated DevOps team
- Special compliance requirements
Example: Banking software that needs custom security configurations
π Pros: Complete control, any software π Cons: You manage everything (updates, security, scaling)
π ECS = Taking the City Bus
Use when:
- You want containerized apps WITHOUT Kubernetes complexity
- Small to medium teams
- Focus on business logic, not infrastructure
- AWS-native integration needed
Example: Web applications, APIs, microservices
π Pros: Easy to use, AWS integrated, less management π Cons: AWS-only, less flexibility than Kubernetes
π EKS = High-Speed Train System
Use when:
- You need Kubernetes features
- Multi-cloud strategy
- Large, complex applications
- Need advanced orchestration
Example: Large enterprise apps, ML pipelines, complex microservices
π Pros: Industry standard, portable, powerful π Cons: Complex, expensive, needs Kubernetes expertise
Read this also:
Simple Decision Tree
Start Here: Do you need containers? β ββ NO β Use EC2 (traditional apps) β ββ YES β Do you know Kubernetes? β ββ YES β And need advanced features? β β β ββ YES β Use EKS β ββ NO β Use ECS (simpler) β ββ NO β Use ECS (much easier to learn)
Real-World Examples π±
Startup Building a Food Delivery App
Team: 3 developers Budget: Limited Choice: ECS β Why: Easy setup, scales automatically, focus on app features
Netflix-Style Video Platform
Team: 50+ engineers Scale: Millions of users Choice: EKS β Why: Complex microservices, need advanced orchestration
Legacy Banking System
Requirements: Specific OS, compliance Existing: 10-year-old Java app Choice: EC2 β Why: Full control needed, gradual modernization
Local Development Setup
git clone https://github.com/iamsanskarjoshi/ecsApp.git
npm i
npm run dev
If we stop the server and start it again, the Capture.png
file still persists.
Deploy Docker App on AWS ECS 2025
FROM node:18-alpine RUN apk add --no-cache curl WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN mkdir -p /mnt/efs/uploads /mnt/efs/documents RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 RUN chown -R nodejs:nodejs /app /mnt/efs USER nodejs EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1 CMD ["npm", "start"]
aws ecr create-repository --repository-name sampleapp --region eu-west-1
aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin <enter-your-aws-accound-id>.dkr.ecr.eu-west-1.amazonaws.com
docker build -t sampleapp .
docker tag sampleapp:latest <enter-your-aws-account-id>.dkr.ecr.eu-west-1.amazonaws.com/sampleapp:latest docker push <enter-your-aws-account-id>.dkr.ecr.eu-west-1.amazonaws.com/sampleapp:latest
Note: Make sure the VPC NACL has both inbound and outbound rules configured to allow the required traffic.
Create ECS Cluster
Create Task Definition
Create Service
task-definition.json
{ "taskDefinitionArn": "arn:aws:ecs:eu-west-1:<enter-your-aws-account-id>:task-definition/sampleAppTaskDefinition:4", "containerDefinitions": [ { "name": "sampleApp", "image": "<enter-your-aws-account-id>.dkr.ecr.eu-west-1.amazonaws.com/sampleApp:latest", "cpu": 0, "portMappings": [ { "name": "sampleApp-3000-tcp", "containerPort": 3000, "hostPort": 3000, "protocol": "tcp", "appProtocol": "http" } ], "essential": true, "environment": [ { "name": "NODE_ENV", "value": "production" }, { "name": "PORT", "value": "3000" }, { "name": "EFS_MOUNT_PATH", "value": "/mnt/efs" } ], "environmentFiles": [], "mountPoints": [ { "sourceVolume": "sharedkeys", "containerPath": "/mnt/efs", "readOnly": false } ], "volumesFrom": [], "ulimits": [], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/sampleApp", "mode": "non-blocking", "awslogs-create-group": "true", "max-buffer-size": "25m", "awslogs-region": "eu-west-1", "awslogs-stream-prefix": "ecs" }, "secretOptions": [] }, "systemControls": [] } ], "family": "sampleApp", "taskRoleArn": "arn:aws:iam::<enter-your-aws-account-id>:role/sampleApp-task-role", "executionRoleArn": "arn:aws:iam::<enter-your-aws-account-id>:role/ecsTaskExecutionRole", "networkMode": "awsvpc", "revision": 22, "volumes": [ { "name": "efs-storage", "efsVolumeConfiguration": { "fileSystemId": "<enter-your-fs-id>", "rootDirectory": "/documents", "transitEncryption": "ENABLED", "authorizationConfig": { "accessPointId": "<enter-your-accesspoint-id>", "iam": "ENABLED" } } } ], "status": "ACTIVE", "requiresAttributes": [ { "name": "ecs.capability.execution-role-awslogs" }, { "name": "com.amazonaws.ecs.capability.ecr-auth" }, { "name": "com.amazonaws.ecs.capability.docker-remote-api.1.28" }, { "name": "com.amazonaws.ecs.capability.task-iam-role" }, { "name": "ecs.capability.execution-role-ecr-pull" }, { "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18" }, { "name": "ecs.capability.task-eni" }, { "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29" }, { "name": "com.amazonaws.ecs.capability.logging-driver.awslogs" }, { "name": "ecs.capability.efsAuth" }, { "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19" }, { "name": "ecs.capability.efs" }, { "name": "com.amazonaws.ecs.capability.docker-remote-api.1.25" } ], "placementConstraints": [], "compatibilities": [ "EC2", "FARGATE" ], "requiresCompatibilities": [ "FARGATE" ], "cpu": "1024", "memory": "3072", "runtimePlatform": { "cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX" }, "registeredAt": "2025-06-30T08:49:30.985Z", "registeredBy": "arn:aws:iam::<enter-your-aws-account-id>:user/jenkins", "enableFaultInjection": false, "tags": [] }
jenkinsfile
pipeline { options { buildDiscarder(logRotator(numToKeepStr: '3')) timeout(time: 30, unit: 'MINUTES') skipStagesAfterUnstable() } agent any environment { // ECR Configuration - Updated to eu-west-1 to match ALB AWS_REGION = 'eu-west-1' ECR_REGISTRY = '<enter-your-aws-account-id>.dkr.ecr.eu-west-1.amazonaws.com' IMAGE_NAME = 'sampleApp' IMAGE_TAG = "${BUILD_NUMBER}" // ECS Configuration ECS_CLUSTER = 'enter cluster name' ECS_SERVICE = '<enter service name>' ECS_TASK_DEFINITION = 'enter task definition' } stages { stage('Docker Build') { steps { script { echo "Building Docker image..." // Build Docker image sh """ docker build -t ${IMAGE_NAME}:${IMAGE_TAG} -t ${ECR_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} . """ // List images sh "docker images | grep ${IMAGE_NAME}" } } } stage('Push to ECR') { steps { script { echo "Pushing image to ECR..." // Login to ECR sh """ aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} """ // Push images sh """ docker push ${ECR_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} """ } } } stage('Update Task Definition') { steps { script { echo "Updating ECS Task Definition..." // Get current task definition sh """ aws ecs describe-task-definition \ --task-definition ${ECS_TASK_DEFINITION} \ --region ${AWS_REGION} \ --query 'taskDefinition' \ --output json > current-task-def.json """ // Update the image in task definition using jq sh """ cat current-task-def.json | \ jq --arg IMAGE "${ECR_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" \ '.containerDefinitions[0].image = \$IMAGE' | \ jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .placementConstraints, .compatibilities, .registeredAt, .registeredBy)' \ > updated-task-def.json """ // Register new task definition sh """ aws ecs register-task-definition \ --cli-input-json file://updated-task-def.json \ --region ${AWS_REGION} """ } } } stage('Deploy to ECS') { steps { script { echo "Deploying to ECS..." // Update ECS service with new task definition sh """ aws ecs update-service \ --cluster ${ECS_CLUSTER} \ --service ${ECS_SERVICE} \ --task-definition ${ECS_TASK_DEFINITION} \ --force-new-deployment \ --region ${AWS_REGION} """ // Wait for deployment to complete sh """ aws ecs wait services-stable \ --cluster ${ECS_CLUSTER} \ --services ${ECS_SERVICE} \ --region ${AWS_REGION} """ } } } } post { always { script { echo "Cleaning up..." // Clean up Docker images sh """ docker rmi -f ${ECR_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} || true docker rmi -f ${IMAGE_NAME}:${IMAGE_TAG} || true """ // Clean up temporary files sh """ rm -f current-task-def.json updated-task-def.json || true """ } } success { script { echo "Pipeline completed successfully!" echo "New image deployed: ${ECR_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" } } failure { script { echo "Pipeline failed!" // Optionally rollback to previous task definition sh """ echo "Consider rolling back if needed" """ } } } }
Installing Jenkins on EC2 Instance 2025: A Step-by-Step Guide