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