How to Deploy Docker App on AWS ECS 2025

5/5 - (2 votes)

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:

https://aws.amazon.com/blogs/containers/amazon-ecs-vs-amazon-eks-making-sense-of-aws-container-services/


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
image 3
npm run dev
image 4
image 5 e1751268903732
image 6

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
image 9
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
image 10
docker build -t sampleapp .
image 11
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
image 12
image

Note: Make sure the VPC NACL has both inbound and outbound rules configured to allow the required traffic.

image 7
image 8

Create ECS Cluster

image 13
image 15
image 16

Create Task Definition

image 14
image 17
image 18
image 19
image 20
image 21

Create Service

image 22
image 23
image 24
image 25
image 26
image 27
image 28
image 29
image 30
image 31
image 32
image 33
image 34
image 35
image 36
image 37
image 38
image 39
image 40
image 41
image 42
image 43
image 44
image 45
image 46
image 54
image 48
image 49
image 50
image 51
image 52
image 55
image 56
image 57
image 58
image 59
image 60
How to Deploy Docker App on AWS ECS

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

Share On:

Leave a Comment