How to Set Up Jenkins for Continuous Deployment on AWS

How to Set Up Jenkins for Continuous Deployment on AWS

AWS gives you a dozen different ways to run an application — EC2, ECS, EKS, Elastic Beanstalk, Lambda — and Jenkins can deploy to all of them from the same core setup. This guide focuses on the general pattern of hosting Jenkins on AWS and deploying to AWS, covering EC2 and ECS as the two most common targets, with the architecture and security groundwork that applies no matter which AWS service you eventually deploy to.

Why Run Jenkins on AWS

Hosting Jenkins itself on an EC2 instance (or in ECS/EKS) keeps your CI/CD pipeline in the same network as your deployment targets, which simplifies IAM permissions, reduces latency for artifact transfers, and lets you use VPC-level security controls instead of exposing deployment credentials over the public internet.

Jenkins Architecture on AWS

  • Controller: Typically runs on a dedicated EC2 instance (or as an ECS/Fargate service), ideally in a private subnet with access via a bastion host or VPN.
  • Agents: Can be static EC2 instances, or dynamically provisioned via the Amazon EC2 Plugin, which spins up agents on demand and terminates them after the build — a good cost optimization for spiky build loads.
  • Artifact storage: S3 is the natural home for build artifacts, deployment packages, and Terraform state.
  • Deployment targets: EC2 Auto Scaling Groups, ECS services, Elastic Beanstalk environments, or Lambda functions.

Prerequisites

  • An AWS account with permissions to create EC2 instances, IAM roles, and security groups
  • Basic familiarity with VPCs, subnets, and security groups
  • Git repository with your application code
  • Jenkins (2.4+ LTS)

Step 1: Launch the Jenkins Controller on EC2

# Launch an EC2 instance (Ubuntu 22.04, t3.medium or larger)
# via AWS Console or CLI, then SSH in and install Jenkins:

sudo apt update
sudo apt install -y fontconfig openjdk-17-jre
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \
  /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
  /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt update
sudo apt install -y jenkins
sudo systemctl enable jenkins
sudo systemctl start jenkins

Attach an IAM instance role to the EC2 instance (rather than static access keys) scoped to whatever the pipeline needs — this is the single most important security decision in this whole setup.

Step 2: Set Up an IAM Instance Role

Example policy for an instance role that deploys to ECS and reads/writes S3 artifacts:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecs:UpdateService",
        "ecs:DescribeServices",
        "ecs:RegisterTaskDefinition",
        "ecs:DescribeTaskDefinition"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::myapp-artifacts/*"
    }
  ]
}

Attach this role to the EC2 instance via IAM > Roles and the EC2 console’s “Modify IAM role” action — no access keys needed anywhere in the pipeline.

Step 3: Install Plugins

  • Amazon EC2 Plugin (for dynamic build agents)
  • AWS Credentials Plugin
  • Pipeline: AWS Steps
  • S3 Publisher Plugin (optional, for artifact uploads)

Step 4: Configure Dynamic Build Agents (Optional but Recommended)

Under Manage Jenkins > Clouds > Add a new cloud > Amazon EC2, configure:

  • AMI ID for your agent image (or use the default Amazon Linux/Ubuntu AMI with a bootstrap script)
  • Instance type (e.g., t3.medium)
  • VPC and subnet
  • Security group allowing SSH/JNLP from the controller
  • Idle termination time (e.g., 15 minutes) so agents don’t run — and cost money — when unused

This means your fleet of build agents scales to zero when nobody’s building anything, and spins up fresh, clean instances on demand.

Step 5: Deploying to EC2 Auto Scaling Groups

A common pattern: build an AMI or deployment package, upload to S3, and trigger a rolling instance refresh.

pipeline {
    agent any

    environment {
        S3_BUCKET = 'myapp-artifacts'
        ASG_NAME = 'myapp-asg'
    }

    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/yourorg/myapp.git'
            }
        }

        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }

        stage('Upload Artifact to S3') {
            steps {
                sh '''
                    aws s3 cp target/myapp.jar s3://$S3_BUCKET/builds/myapp-$BUILD_NUMBER.jar
                '''
            }
        }

        stage('Update Launch Template') {
            steps {
                sh '''
                    aws ec2 create-launch-template-version \
                      --launch-template-name myapp-launch-template \
                      --source-version 1 \
                      --launch-template-data '{"UserData":"'"$(base64 -w0 userdata.sh)"'"}'
                '''
            }
        }

        stage('Trigger Instance Refresh') {
            steps {
                sh '''
                    aws autoscaling start-instance-refresh \
                      --auto-scaling-group-name $ASG_NAME \
                      --preferences '{"MinHealthyPercentage": 90, "InstanceWarmup": 120}'
                '''
            }
        }

        stage('Wait for Refresh Completion') {
            steps {
                sh '''
                    while true; do
                        STATUS=$(aws autoscaling describe-instance-refreshes \
                          --auto-scaling-group-name $ASG_NAME \
                          --query 'InstanceRefreshes[0].Status' --output text)
                        echo "Refresh status: $STATUS"
                        if [ "$STATUS" == "Successful" ]; then break; fi
                        if [ "$STATUS" == "Failed" ] || [ "$STATUS" == "Cancelled" ]; then
                            exit 1
                        fi
                        sleep 15
                    done
                '''
            }
        }
    }
}

Instance refresh gives you rolling deployment behavior with a configurable minimum healthy percentage — a built-in safety net against a bad AMI taking down the whole fleet at once.

Step 6: Deploying to ECS

For containerized applications on ECS (with or without Fargate):

stage('Build and Push to ECR') {
    steps {
        sh '''
            aws ecr get-login-password --region us-east-1 | \
              docker login --username AWS --password-stdin $ECR_REPO
            docker build -t $ECR_REPO:$BUILD_NUMBER .
            docker push $ECR_REPO:$BUILD_NUMBER
        '''
    }
}

stage('Register New Task Definition') {
    steps {
        sh '''
            NEW_TASK_DEF=$(aws ecs describe-task-definition --task-definition myapp-task \
              --query 'taskDefinition' | \
              jq --arg IMAGE "$ECR_REPO:$BUILD_NUMBER" \
              '.containerDefinitions[0].image = $IMAGE | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)')
            echo "$NEW_TASK_DEF" > new-task-def.json
            aws ecs register-task-definition --cli-input-json file://new-task-def.json
        '''
    }
}

stage('Update ECS Service') {
    steps {
        sh '''
            aws ecs update-service --cluster myapp-cluster --service myapp-service \
              --task-definition myapp-task --force-new-deployment
        '''
    }
}

stage('Wait for Service Stability') {
    steps {
        sh 'aws ecs wait services-stable --cluster myapp-cluster --services myapp-service'
    }
}

aws ecs wait services-stable blocks until ECS confirms the new tasks are healthy and old ones have drained — Jenkins won’t report success until the deployment has genuinely stabilized.

Infrastructure as Code Integration

For anything beyond the simplest setup, define your VPC, security groups, ASG, ECS cluster, and IAM roles in Terraform or CloudFormation rather than manual console clicks, and let a separate Jenkins pipeline (or stage) apply infrastructure changes independently from application deployments.

Real-World Workflow

  1. Code merges to main → Jenkins builds and pushes an image or artifact.
  2. Deployment stage updates the target (ASG instance refresh or ECS service update).
  3. Jenkins blocks on a wait/health-check step until AWS confirms the new version is stable.
  4. CloudWatch alarms tied to the deployed resources notify the team if error rates spike post-deploy.
  5. A rollback stage (redeploy previous task definition revision, or roll back the ASG launch template) is available as a one-click parameterized job.

Security Best Practices

  • Use IAM instance roles for the Jenkins controller and agents — never store long-lived AWS access keys on disk.
  • Scope IAM policies to specific resource ARNs, not *, especially for anything with write access.
  • Put the Jenkins controller in a private subnet, accessible only via VPN, bastion host, or an internal load balancer.
  • Enable CloudTrail logging on all deployment-related API calls for auditability.
  • Encrypt S3 artifact buckets and enable versioning so you can always retrieve a previous build.

Troubleshooting

  • EC2 agents fail to connect back to the controller: Check the security group allows inbound JNLP (default port 50000) from the agent’s security group, and that the agent AMI has Java installed.
  • Instance refresh stuck in “Pending”: Usually a health check misconfiguration — confirm the target group health check path returns 200 quickly after instance boot.
  • ECS service stuck in “PROVISIONING”: Check task definition CPU/memory fits within the cluster’s available capacity, and that the task role has permissions to pull from ECR.

FAQs

Should I host Jenkins on AWS or use a managed alternative like AWS CodePipeline? Both are valid; Jenkins gives you portability and a huge plugin ecosystem, while CodePipeline is more tightly integrated with AWS-native services and requires less maintenance. Many teams use Jenkins for the CI/build/test stages and let it trigger CodeDeploy for the actual AWS-native deployment mechanics.

How do I keep Jenkins agent costs down? Use the EC2 plugin’s dynamic provisioning with aggressive idle-termination settings, or run agents as Fargate Spot tasks for further savings on non-critical builds.

Can Jenkins deploy across multiple AWS accounts? Yes, using cross-account IAM roles — the Jenkins agent’s role assumes a role in the target account via sts:AssumeRole before running deployment commands.

What’s the fastest way to roll back a bad ECS deployment? Re-run aws ecs update-service pointing at the previous task definition revision number — ECS keeps prior revisions around by default, making rollback a single command.

Summary

Running Jenkins for AWS deployments comes down to getting the IAM foundation right — instance roles over static keys, scoped permissions, and a private network posture — then building deployment stages around whichever AWS compute service you’re targeting. Whether that’s an EC2 Auto Scaling Group refresh or an ECS service update, the same wait-for-stability discipline keeps deployments safe and observable.

References

Total
1
Shares

Leave a Reply

Previous Post
How to Use Jenkins with Azure DevOps for CI/CD

How to Use Jenkins with Azure DevOps for CI/CD

Next Post
How to Implement Blue-Green Deployments with Jenkins

How to Implement Blue-Green Deployments with Jenkins

Related Posts