The Lambda Runtime Spectrum: From Simple to Sophisticated
How to choose the right level of complexity for your serverless functions
The Runtime Complexity Problem
AWS Lambda gives you runtime choices: Python, Node.js, Java, Go, .NET, Ruby. But what if your problem doesn’t need a “real” programming language?
What if you just need curl + jq?
The Traditional Approach
The problem: You need an API that calls another API and transforms the response.
The default thinking: “I need to build an API, so I need a web framework in a real language.”
The result:
const axios = require('axios');
exports.handler = async (event) => {
const response = await axios.get(externalUrl);
return { statusCode: 200, body: JSON.stringify(response.data) };
};
The overhead: Runtime initialization, dependency loading, JSON parsing libraries, HTTP client abstractions.
The Spectrum Approach
Instead of one-size-fits-all, what if you could choose the right level of complexity?
Level 1: Simple Bash Functions
When to use: Basic HTTP calls, simple data transformation, file processing
Runtime: provided.al2023 with custom bootstrap
Package: Zip artifact with shell scripts
Memory: ~15MB
Cold start: ~200ms
#!/bin/bash
api_handler() {
local event="$1"
curl -s "https://api.example.com/data" | jq '.results'
}
Perfect for:
- HTTP proxies
- Simple data transformation
- File processing
- Basic automation
Level 2: Enhanced with Layers
When to use: Need specific CLI tools beyond basic shell
Runtime: provided.al2023 + pre-built layers
Available tools: jq, qrencode, htmlq, imagemagick, pandoc, sqlite, yq
Memory: ~25MB
Cold start: ~250ms
#!/bin/bash
qr_generator() {
local text="$1"
echo "$text" | qrencode -o - -t PNG | base64
}
html_parser() {
local url="$1"
curl -s "$url" | htmlq '.title' --text
}
Perfect for:
- Web scraping with HTML parsing
- Image processing
- QR code generation
- Document conversion
- Data extraction
Level 3: Custom Runtime (Tiny)
When to use: Need consistent tooling, container deployment
Runtime: Custom container with jq, curl, http-cli
Size: 132MB
Memory: ~30MB
Cold start: ~300ms
#!/bin/bash
api_client() {
local endpoint="$1"
local params="$2"
http-cli --json "$endpoint" \
--data "$params" \
--timeout 10 \
| jq '.data | map(select(.active == true))'
}
Perfect for:
- Production HTTP APIs
- JSON-heavy processing
- Consistent deployment pipeline
Level 4: Custom Runtime (Micro)
When to use: Need AWS API calls without full CLI overhead
Runtime: Custom container + awscurl
Size: 221MB
Memory: ~50MB
Cold start: ~400ms
#!/bin/bash
s3_processor() {
local bucket="$1"
local key="$2"
# Get object metadata
awscurl --service s3 "https://s3.amazonaws.com/$bucket/$key" \
--request HEAD \
| grep -i last-modified
}
Perfect for:
- AWS service integration
- S3 operations
- Simple AWS API calls
Level 5: Custom Runtime (Full)
When to use: Need complete AWS CLI functionality
Runtime: Custom container + full AWS CLI Size: 417MB Memory: ~80MB Cold start: ~500ms
#!/bin/bash
infrastructure_manager() {
local action="$1"
case "$action" in
"backup")
aws s3 sync /data s3://backup-bucket/$(date +%Y%m%d)
;;
"deploy")
aws cloudformation deploy --template-file template.yml
;;
esac
}
Perfect for:
- Infrastructure automation
- Complex AWS operations
- Multi-service orchestration
Choosing the Right Level
Ask these questions:
-
What tools do I actually need?
- Just curl + jq? → Level 1
- Need image processing? → Level 2
- Need AWS APIs? → Level 4
-
What’s my performance requirement?
- Sub-200ms response? → Level 1-2
- Can tolerate 500ms cold start? → Level 5
-
What’s my deployment preference?
- Simple zip upload? → Level 1-2
- Container-based CI/CD? → Level 3-5
-
What’s my complexity tolerance?
- Keep it simple? → Level 1
- Need production features? → Level 3+
The Anti-Pattern
Don’t start with Level 5 and work down.
Most developers start with “I need the full AWS CLI” and end up with 400MB containers for simple HTTP calls.
Start with Level 1 and work up.
Begin with the simplest solution. Add complexity only when you hit real limits.
Real-World Examples
HTTP Proxy API:
- Started with Node.js (traditional)
- Moved to Level 1 (bash + curl)
- Result: 2x faster, 90% less memory
Image Processing Service:
- Could use Level 5 (full runtime)
- Actually needs Level 2 (imagemagick layer)
- Result: 50% smaller, faster deployment
AWS Resource Monitor:
- Tempting to use Level 5 (full CLI)
- Level 4 (awscurl) handles 80% of cases
- Result: Faster cold starts, smaller footprint
The Infrastructure Philosophy
The runtime should match the problem, not your assumptions.
This is why I built the lambda-shell-runtime spectrum:
- Give developers the choice
- Make simple things simple
- Make complex things possible
- No vendor lock-in at any level
Conclusion
Not every Lambda function needs a programming language.
Sometimes you need the full power of Python or Node.js. Sometimes you just need curl.
The infrastructure should get out of your way and let you choose the right tool for the job.
Start simple. Scale complexity only when necessary.
Part of the cloudless philosophy - infrastructure that gets out of your way.