Guide: Build and Deploy Docker Image to AWS ECS Cluster with Jenkins
devops

Guide: Build and Deploy Docker Image to AWS ECS Cluster with Jenkins

COPPER Nguyen

This guide demonstrates how to build a Docker image, push it to AWS ECR, and deploy it to an ECS cluster using a Jenkins pipeline. AWS credentials are securely injected using AmazonWebServicesCredentialsBinding. We'll also show how to use a notification tool.

Prerequisites

  • AWS account with ECS and ECR permissions
  • Jenkins with AWS Credentials plugin installed
  • Docker installed on Jenkins agent
  • Notification tool/library (e.g., Teams webhook, Slack, etc.)

1. Jenkins Pipeline Example

Below is a Jenkins pipeline script that covers the full process:

@Library('mylibrary') _

def ECR_ROOT = "<your-account-id>.dkr.ecr.ap-northeast-1.amazonaws.com"
def ECR_REPO_NAME = "my-app"
def ECS_CLUSTER = "my-cluster"
def ECS_SERVICE = "my-service"
def ECS_TASK_FAMILY = "my-task-family"

node("your-agent-label") {
    stage("Checkout SCM") {
        checkout scmGit(
            branches: [[name: "main"]],
            userRemoteConfigs: [[credentialsId: "your.git.credentials",
            url: "git@github.com:your-org/your-repo.git"]]
        )
    }
    
    
    withCredentials([
        [$class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'MY-AWS-ACCOUNT'],
    ]) {
    
    
        stage("Build Docker Image") {
            sh """
            docker build -t ${ECR_REPO_NAME}:latest .
            aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${ECR_ROOT}
            docker tag ${ECR_REPO_NAME}:latest ${ECR_ROOT}/${ECR_REPO_NAME}:\${BUILD_ID}
            docker push ${ECR_ROOT}/${ECR_REPO_NAME}:\${BUILD_ID}
            """
        }
        
        
        stage("Deploy to ECS") {
            sh """
            TASK_DEFINITION=\$(aws ecs describe-task-definition --task-definition "${ECS_TASK_FAMILY}")
            NEW_IMAGE="${ECR_ROOT}/${ECR_REPO_NAME}:\${BUILD_ID}"
            echo \$TASK_DEFINITION | jq --arg IMAGE "\$NEW_IMAGE" '.taskDefinition | .containerDefinitions[0].image = \$IMAGE | del(.taskDefinitionArn) | del(.revision) | del(.status) | del(.requiresAttributes) | del(.compatibilities) | del(.registeredAt) | del(.registeredBy)' > new-task-def.json
            NEW_TASK_DEFINITION_ARN=\$(aws ecs register-task-definition --cli-input-json file://new-task-def.json | jq -r ".taskDefinition.taskDefinitionArn")
            aws ecs update-service --cluster ${ECS_CLUSTER} --service ${ECS_SERVICE} --task-definition \$NEW_TASK_DEFINITION_ARN --force-new-deployment
            aws ecs wait services-stable --cluster ${ECS_CLUSTER} --service ${ECS_SERVICE}
            """
        }
        
        
        stage("Notification") {
            def commitMessage = sh(script: "git log -1 --pretty=%B", returnStdout: true).trim()
            msteams.sendSuccessToMyTeam(ECS_SERVICE, commitMessage)
        }
    }
}

2. Explanation of Each Jenkins Pipeline Stage

Let's break down what each stage in the pipeline does:

  • Checkout SCM
    Checks out your source code from the specified Git repository and branch, ensuring the pipeline works with the latest code.
  • Build Docker Image
    Builds the Docker image using your Dockerfile, authenticates Docker to AWS ECR, tags the image with the build ID, and pushes it to your ECR repository.
  • Deploy to ECS
    Retrieves the current ECS task definition, updates the container image to the new version, registers a new task definition revision, and updates the ECS service to use this new revision. Waits for the ECS service to become stable.
  • Notification
    Extracts the latest commit message and sends a deployment notification using your notification tool (e.g., Microsoft Teams, Slack).

3. Using Shared Libraries for Reusable Pipeline Functions

To avoid code duplication and promote maintainability, you should use Jenkins Shared Libraries for common functions such as notifications, code checkout, or deployment logic. Shared libraries allow you to define reusable Groovy scripts and import them into multiple pipelines with the @Library annotation.

Example:

@Library('mylibrary') _

node {
    // Now you can use functions like msteams.sendSuccessToMyTeam, commit.extractCommit, etc.
}

By centralizing common logic in a shared library, you ensure consistency and make updates easier across all your pipelines.

4. Notes

  • AWS Credentials: The AmazonWebServicesCredentialsBinding injects AWS credentials as environment variables for the shell steps.
  • Notification: The example uses a custom msteams.sendSuccessToMyTeam function. Replace or adapt this for your notification tool.
  • Security: Never hardcode credentials. Always use Jenkins credentials binding.

Tip: Adjust image names, repository, and service names as needed for your environment.