How to Set Up Jenkins for Continuous Deployment to AWS Lambda

How to Set Up Jenkins for Continuous Deployment to AWS Lambda

Serverless computing has changed the way I think about shipping code. No servers to patch, no capacity planning at 2 AM, just functions that scale on demand. But serverless doesn’t mean you get to skip automation — if anything, a solid CI/CD pipeline matters more when you’re deploying dozens of small functions instead of one monolith. In this guide I’ll walk through exactly how to wire Jenkins up to deploy code straight to AWS Lambda, from the first apt install to a production-grade pipeline with rollback safety built in.

Why Jenkins for Lambda Deployments

AWS has its own native tools (CodePipeline, CodeBuild, SAM), so a fair question is: why bother with Jenkins? The honest answer is flexibility and control. Jenkins isn’t tied to AWS, so it fits naturally into a multi-cloud or hybrid setup, it has a massive plugin ecosystem, and it gives fine-grained control over every stage of the pipeline — testing, packaging, security scanning, approvals — before a single byte touches Lambda.

Jenkins Architecture Refresher

Before touching Lambda, it helps to understand what’s actually happening under the hood in Jenkins.

For Lambda deployments specifically, the controller triggers a pipeline job on a webhook from your Git repository, an agent checks out the code, runs tests, packages the function, and then pushes the artifact to AWS using the AWS CLI or SDK.

Prerequisites

Step 1: Install Jenkins

On an Ubuntu server:

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

Unlock Jenkins by visiting http://<server-ip>:8080 and pasting the initial admin password from /var/lib/jenkins/secrets/initialAdminPassword. Install the suggested plugins to get Git, Pipeline, and Credentials support out of the box.

Step 2: Install Required Plugins

Go to Manage Jenkins > Plugins and install:

Step 3: Install and Configure the AWS CLI on the Agent

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
aws --version

Step 4: Set Up IAM Permissions

Create a dedicated IAM user (or better, use an IAM role if Jenkins runs on EC2) with a policy scoped to what deployments actually need:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "lambda:UpdateFunctionCode",
        "lambda:UpdateFunctionConfiguration",
        "lambda:GetFunction",
        "lambda:PublishVersion",
        "lambda:CreateAlias",
        "lambda:UpdateAlias"
      ],
      "Resource": "arn:aws:lambda:*:*:function:my-function-*"
    }
  ]
}

Store the access key and secret in Jenkins under Manage Jenkins > Credentials as an “AWS Credentials” type entry. Never hardcode keys in the Jenkinsfile.

Step 5: Write the Jenkinsfile

Here’s a working declarative pipeline for a Node.js Lambda function:

pipeline {
    agent any

    environment {
        AWS_DEFAULT_REGION = 'us-east-1'
        FUNCTION_NAME = 'my-node-lambda'
    }

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

        stage('Install Dependencies') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Run Tests') {
            steps {
                sh 'npm test'
            }
        }

        stage('Package') {
            steps {
                sh 'zip -r function.zip . -x "*.git*"'
            }
        }

        stage('Deploy to Lambda') {
            steps {
                withCredentials([[
                    $class: 'AmazonWebServicesCredentialsBinding',
                    credentialsId: 'aws-lambda-deploy-creds'
                ]]) {
                    sh '''
                        aws lambda update-function-code \
                          --function-name $FUNCTION_NAME \
                          --zip-file fileb://function.zip
                    '''
                }
            }
        }

        stage('Publish Version & Alias') {
            steps {
                withCredentials([[
                    $class: 'AmazonWebServicesCredentialsBinding',
                    credentialsId: 'aws-lambda-deploy-creds'
                ]]) {
                    sh '''
                        VERSION=$(aws lambda publish-version --function-name $FUNCTION_NAME --query 'Version' --output text)
                        aws lambda update-alias --function-name $FUNCTION_NAME --name live --function-version $VERSION
                    '''
                }
            }
        }
    }

    post {
        success {
            echo "Deployment to Lambda succeeded."
        }
        failure {
            echo "Deployment failed — check logs above."
        }
    }
}

Deploying Container-Based Lambda Functions

If your function ships as a container image instead of a zip, the pipeline needs an extra step to build and push to Amazon ECR before updating Lambda:

stage('Build and Push Image') {
    steps {
        sh '''
            aws ecr get-login-password --region $AWS_DEFAULT_REGION | \
              docker login --username AWS --password-stdin $ECR_REPO
            docker build -t $ECR_REPO:$BUILD_NUMBER .
            docker push $ECR_REPO:$BUILD_NUMBER
        '''
    }
}
stage('Update Lambda Image') {
    steps {
        sh '''
            aws lambda update-function-code \
              --function-name $FUNCTION_NAME \
              --image-uri $ECR_REPO:$BUILD_NUMBER
        '''
    }
}

Infrastructure as Code with Terraform or SAM

For anything beyond a toy project, define your Lambda function, IAM role, and triggers in Terraform or AWS SAM rather than clicking around the console. A common pattern is to have Jenkins run terraform apply for infrastructure changes and a separate, faster stage for code-only deployments. This separation keeps day-to-day deploys quick while still versioning infrastructure changes properly.

Real-World Workflow: Git to Production

A typical flow I use looks like this:

  1. Developer pushes to a feature branch → Jenkins runs unit tests only.
  2. PR merged to main → Jenkins builds, tests, packages, and deploys to a staging alias.
  3. Manual approval step in Jenkins (input step) gates the promotion to production alias.
  4. Post-deploy smoke test hits the API Gateway endpoint tied to the Lambda alias to confirm a 200 response.

Add a manual gate like this:

stage('Approve Production Deploy') {
    steps {
        input message: 'Promote to production?', ok: 'Deploy'
    }
}

Monitoring and Rollback

Because Lambda supports versioning and aliases, rollback is just repointing the alias to the previous version:

aws lambda update-alias --function-name my-node-lambda --name live --function-version 42

Wire CloudWatch alarms on error rate and duration, and consider having Jenkins poll CloudWatch metrics right after deployment as an automated canary check before fully cutting traffic over.

Security Best Practices

Troubleshooting Common Issues

Testing Lambda Functions Locally Before Deployment

Catching issues before they reach AWS saves both time and the awkwardness of a failed production deploy. Tools like sam local invoke let you run a Lambda function locally against a sample event, simulating the real Lambda runtime environment closely enough to catch most integration issues:

stage('Local Invoke Test') {
    steps {
        sh '''
            sam local invoke MyFunction \
              --event events/test-event.json \
              --template template.yaml
        '''
    }
}

Add this as a stage between packaging and deployment so a broken handler function fails the pipeline immediately rather than surfacing as a runtime error after the update has already gone live.

FAQs

Can Jenkins deploy to multiple AWS regions in one pipeline? Yes — parametrize the region and loop the deploy stage, or run parallel stages targeting different AWS_DEFAULT_REGION values.

Do I need SAM or the Serverless Framework if I’m using Jenkins? No, but they can coexist. Jenkins can simply shell out to sam deploy or serverless deploy instead of raw AWS CLI calls if you prefer their conventions.

How do I handle secrets that the Lambda function itself needs at runtime? Use AWS Secrets Manager or SSM Parameter Store and fetch them inside the function code — don’t bake secrets into the deployment package.

What triggers should I use to start the Jenkins pipeline? A GitHub webhook on push/PR-merge events is the most common approach; poll-based SCM triggers work too but add latency.

Summary

Setting up Jenkins for Lambda deployments comes down to a handful of solid building blocks: a properly scoped IAM identity, the AWS CLI on your agents, a clean Jenkinsfile with build, test, package, and deploy stages, and version/alias management for safe rollbacks. Once this is in place, shipping a new Lambda function version becomes as routine as merging a pull request.

References

Exit mobile version