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.
- Jenkins Controller (Master): Hosts the web UI, stores configuration, schedules jobs, and dispatches work to agents.
- Agents (Nodes): Separate machines or containers that actually execute build steps. Keeping builds off the controller is a best practice for both security and performance.
- Executors: Slots on an agent that run one build step at a time.
- Jobs/Pipelines: Defined either through the classic UI or, preferably, as code using a
Jenkinsfile. - Plugins: Extend Jenkins with support for source control, cloud providers, notifications, and more.
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
- A Jenkins server (v2.4+, LTS recommended) — self-hosted EC2 instance, on-prem VM, or Docker container
- An AWS account with an IAM user or role scoped for Lambda deployments
- AWS CLI installed on the Jenkins agent
- Your function code in a Git repository (GitHub, GitLab, Bitbucket, etc.)
- Basic familiarity with either Node.js, Python, or Java, since Lambda functions are usually written in one of these
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:
- Pipeline (usually pre-installed)
- Git
- AWS Credentials
- Amazon ECR (if you’re deploying container-based Lambda functions)
- Pipeline: AWS Steps (optional, wraps AWS SDK calls as pipeline steps)
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:
- Developer pushes to a feature branch → Jenkins runs unit tests only.
- PR merged to
main→ Jenkins builds, tests, packages, and deploys to astagingalias. - Manual approval step in Jenkins (
inputstep) gates the promotion toproductionalias. - 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
- Use least-privilege IAM policies scoped to specific function ARNs, never
lambda:*on*. - Store credentials in Jenkins Credentials Manager, not environment variables in job configuration.
- Enable audit logging via AWS CloudTrail for all Lambda deployment API calls.
- Scan dependencies for vulnerabilities before packaging (e.g.,
npm auditorpip-audit) as a pipeline stage. - Rotate IAM access keys regularly, or better, use OIDC federation between Jenkins and AWS to avoid long-lived keys entirely.
Troubleshooting Common Issues
- “Access Denied” errors: Almost always an IAM policy scoping issue — check the exact action and resource ARN.
- Zip file too large: Lambda has a 50 MB direct upload limit; upload to S3 first and reference the S3 key instead.
- Cold start complaints after deploy: Normal after a code update since a new execution environment is provisioned; not a deployment failure.
- Alias not routing traffic: Confirm API Gateway or the invoking service is pointing at the alias ARN, not the
$LATESTversion.
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.