As a systems automation engineer at Oath, I built a custom continuous-delivery solution for AWS Lambda on top of Screwdriver, the open-source CI/CD platform Oath maintained. The goal was simple to state and not simple to build: an engineer should be able to drop a single YAML file into their repo and have their function deploy — no knowing CloudFormation, no hand-rolled IAM, no bespoke packaging script per team. It was language agnostic, so the same workflow shipped Python, Node, Go, and Java functions the same way.
Deploying a Lambda looks trivial — it’s a zip upload — right up until you’re doing it for dozens of teams, in four languages, safely, every day. This post is about closing that gap: a quick primer on Lambda, then how you actually build continuous delivery around it — the manifest, the versioning, and the rollout.
A quick primer
Lambda runs code without a server. You hand AWS a function and tell it what triggers it; AWS runs it on demand, scales it out under load, bills you per invocation, and costs nothing when idle — it scales to zero. A few properties shape everything downstream, including how you deploy it:
- Event-driven. A function is invoked by an event — an HTTP request via API Gateway, a new object in S3, a queue message, a scheduled timer.
- Stateless and ephemeral. Each invocation runs in an environment AWS may create, reuse, or discard, so state lives in a database or object store — which is exactly what makes versioning a function clean.
- Packaged per runtime. You ship code and its dependencies as a zip (or image); how you build that bundle depends on the language.
- Horizontal concurrency. Ten simultaneous events means ten copies running at once — no threading model to reason about, AWS just runs more of them.
It’s a great fit for event-shaped, bursty work — webhooks, S3 and stream processing, scheduled jobs, spiky APIs — and a poor one for long-running jobs, heavy sustained compute, or anything needing consistently low latency (cold starts hurt). The rule of thumb: event-shaped and intermittent → Lambda; continuous → a container or server.
That’s the context. The actual engineering is in shipping them.
Why deploying a Lambda needs real CD
Here’s the trap: deploying a Lambda looks like uploading a zip, so teams start with a shell script that does exactly that. Then reality arrives. A real deployment has to:
- package the code and its dependencies correctly for the runtime,
- attach the right IAM role with least-privilege permissions,
- set environment variables and memory/timeout config,
- wire up the triggers (S3, API Gateway, EventBridge, …),
- publish a new version and move traffic to it safely, with a way back.
Multiply that across dozens of teams and four languages and the pile of per-team scripts becomes its own maintenance problem. That’s the problem the platform I built existed to solve.
The drop-in manifest
The interface was one declarative file. An engineer described what they wanted, and the pipeline handled how:
# lambda.yml — drop this in your repo and the pipeline does the rest.
name: image-thumbnailer
runtime: python3.8 # nodejs / go / java too — the pipeline packages per runtime
handler: app.handler
memory: 512
timeout: 30
environment:
BUCKET: media-thumbnails
triggers:
- type: s3
bucket: media-uploads
events: [s3:ObjectCreated:*]
role: arn:aws:iam::123456789012:role/thumbnailerThe language-agnostic part came from convention over configuration. The runtime field told the pipeline how to build: requirements.txt meant pip install into the
package, package.json meant npm install, a Go module meant go build. The engineer
never wrote packaging logic — they declared a runtime and followed their language’s
normal dependency convention. Everything downstream of the zip was identical regardless
of language, which is what let one platform serve every team.
The reason a declarative file beats a clever script: it’s reviewed in a pull request like any other code, it’s the same across every service, and it moves all the genuinely hard parts — IAM, packaging, rollout, rollback — out of the engineer’s hands and into a platform that gets them right once.
Versions, aliases, and safe rollouts
The mechanism that makes Lambda CD safe is versions and aliases. Publishing creates
an immutable, numbered version of your function. An alias (say, prod) is a named
pointer to a version. Nothing calls a version number directly — they call the alias.
That indirection is the whole game:
- Deploy = publish a new version, then point the
prodalias at it. - Rollback = point the alias back at the previous version. Instant, no rebuild.
- Canary = give the alias a weighted config so 10% of traffic hits the new version while 90% stays on the old one, then ramp up if the metrics stay clean.
Get this right and a bad deploy is a one-command recovery instead of a scramble.
A minimal pipeline
You don’t need a big platform to get the core of this. Here’s the shape of it as a Jenkinsfile — package, publish a version, shift the alias:
pipeline {
agent any
stages {
stage('Package') {
steps {
sh '''
pip install -r requirements.txt -t build/
cp -r src/* build/
(cd build && zip -qr ../function.zip .)
'''
}
}
stage('Deploy') {
steps {
sh 'aws lambda update-function-code
--function-name image-thumbnailer
--zip-file fileb://function.zip --publish > version.json'
script {
def version = sh(script: 'jq -r .Version version.json',
returnStdout: true).trim()
// Shift prod to the new version. To roll back, run this with the
// previous number. To canary, add --routing-config instead.
sh "aws lambda update-alias --function-name image-thumbnailer
--name prod --function-version ${version}"
}
}
}
}
}Swap the Package stage’s build commands per runtime and you have the language-agnostic
idea in miniature — the deploy stage doesn’t care what language produced the zip.
Where to start
You almost never need to build this from scratch anymore — the ecosystem has grown up. For defining and shipping functions, the Serverless Framework, AWS SAM, and the AWS CDK all give you the declarative-manifest experience out of the box, and Terraform covers it if you’re already invested there. For the orchestration and progressive-delivery layer, a CD platform like Spinnaker or Screwdriver handles the versioned, canaried rollouts I described above.
The one I’d reach for first today is SAM or the Serverless Framework wired into whatever CI you already run. But the underlying ideas don’t change no matter which you pick: a declarative manifest as the interface, and versions plus aliases as the safety net. Get those two right and everything else is detail.