TL;DR
A step-by-step guide to creating, deploying, and managing AWS Lambda functions using container images, including best practices and optimization techniques.
export const components = {
AWSLambda,
AWSContainerRegistry,
AWSCloudWatch,
AWSXRay,
AWSCloudTrail,
AWSS3,
AWSAPIGateway,
AWSSQSQueue,
AWSEventBridge,
AWSCloudDevelopmentKit,
Arrow
};
Introduction
AWS Lambda's container image support represents a significant evolution in serverless computing, allowing developers to package and deploy functions using container images up to 10GB in size. This capability bridges the gap between containerized applications and serverless architecture, offering greater flexibility in dependency management and code deployment.
$1
$1
Before diving in, ensure you have:
| Tool/Resource | Version | Purpose |
|---|---|---|
| AWS Account | N/A | For deploying Lambda functions |
| Docker | 20.10+ | For building container images |
| AWS CLI | 2.x | For AWS interactions |
| IDE | Any | Code development |
| Python | 3.11+ | For sample applications |
$1
$1
| Aspect | ZIP Deployment | Container Image |
|---|---|---|
| Size Limit | 250MB | 10GB |
| Cold Start | Faster | Slightly slower |
| Build Tools | Limited | Full Docker ecosystem |
| Dependencies | Layer management | Docker layers |
| Portability | Lambda-specific | Multi-platform |
$1
1. Consistent Development Experience
- Use familiar Docker workflows
- Same tools across local and cloud
- Better development-production parity
2. Advanced Dependency Management
- Layer caching for faster builds
- Better control over dependencies
- Support for complex requirements
3. Enhanced Security
- Container scanning integration
- Image signing capabilities
- Vulnerability assessments
$1
{/ Developer and Local Environment /}
Developer
{/ Build and Push Process /}
Amazon ECR
{/ Lambda Function /}
AWS Lambda
{/ Event Sources /}
S3 Events
API Gateway
SQS Queue
EventBridge
{/ Monitoring and Logging /}
CloudWatch Logs
X-Ray Tracing
CloudTrail
$1
$1
`` {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
],
"Resource": "*"
}
]
}
json
`
$1
| Environment | Local | Development | Production |
|---|---|---|---|
| Base Image | Development | Optimized | Optimized |
| Dependencies | All | Production | Production |
| Environment Variables | Local | AWS Secrets | AWS Secrets |
| Logging | Console | CloudWatch | CloudWatch |
| Monitoring | Basic | Enhanced | Full |
$1
$1
` lambda-container/
├── Dockerfile
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── utils/
│ │ ├── __init__.py
│ │ └── helpers.py
│ └── config/
│ ├── __init__.py
│ └── settings.py
├── tests/
│ ├── __init__.py
│ ├── test_handler.py
│ └── test_utils.py
├── requirements/
│ ├── base.txt
│ ├── dev.txt
│ └── prod.txt
└── scripts/
├── build.sh
└── deploy.sh
plaintext
`
$1
` import json
import logging
from typing import Dict, Any
from datetime import datetime logger = logging.getLogger()
logger.setLevel(logging.INFO) def setup_logging() -> None:
"""Configure logging with JSON formatter"""
formatter = logging.Formatter(
'{"timestamp": "%(asctime)s", "level": "%(levelname)s", "message": %(message)s}'
)
for handler in logger.handlers:
handler.setFormatter(formatter) def process_event(event: Dict[str, Any]) -> Dict[str, Any]:
"""Process the incoming event with error handling"""
try:
logger.info(json.dumps({"message": "Processing event", "event": event}))
# Add your business logic here
result = {
"processed_at": datetime.utcnow().isoformat(),
"event_data": event
}
return result
except Exception as e:
logger.error(json.dumps({"message": "Error processing event", "error": str(e)}))
raise def handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
"""Main Lambda handler function"""
setup_logging()
try:
result = process_event(event)
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps({
'message': 'Success',
'data': result
})
}
except Exception as e:
return {
'statusCode': 500,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps({
'message': 'Error',
'error': str(e)
})
}
python
`app/main.py
Configure logging
$1
` requests==2.31.0
boto3==1.34.11
python-json-logger==2.0.7
pydantic==2.5.2 -r base.txt
pytest==7.4.3
pytest-cov==4.1.0
black==23.11.0
mypy==1.7.1 -r base.txt
watchtower==3.0.1
aws-xray-sdk==2.12.1
plaintext
`requirements/base.txt
requirements/dev.txt
requirements/prod.txt
$1
` FROM public.ecr.aws/lambda/python:3.11 as builder COPY requirements/prod.txt .
RUN pip install --no-cache-dir -r prod.txt -t python/ FROM public.ecr.aws/lambda/python:3.11-slim COPY --from=builder python/ ${LAMBDA_TASK_ROOT} COPY app/ ${LAMBDA_TASK_ROOT}/app/ ENV PYTHONPATH=${LAMBDA_TASK_ROOT}
ENV LOG_LEVEL=INFO CMD [ "app.main.handler" ]
dockerfile
`Build stage
Copy requirements
Runtime stage
Copy only production dependencies
Copy application code
Set environment variables
Set the handler
$1
$1
| Feature | Console | CLI | Infrastructure as Code |
|---|---|---|---|
| Automation | Manual | Scripts | Full automation |
| Version Control | Limited | Git | Git + Templates |
| Rollback | Manual | Automated | Automated |
| Environment Variables | UI | CLI/Files | Template |
| Resource Management | Manual | Scripted | Declarative |
$1
` AWSTemplateFormatVersion: '2010-09-09'
Description: 'Lambda Container Function Stack' Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
ImageUri:
Type: String
Description: ECR image URI Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole LambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-${Environment}
PackageType: Image
Code:
ImageUri: !Ref ImageUri
Role: !GetAtt LambdaExecutionRole.Arn
MemorySize: 1024
Timeout: 30
Environment:
Variables:
ENVIRONMENT: !Ref Environment
LOG_LEVEL: INFO LambdaLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${LambdaFunction}
RetentionInDays: 30 Outputs:
FunctionName:
Description: Lambda function name
Value: !Ref LambdaFunction
FunctionArn:
Description: Lambda function ARN
Value: !GetAtt LambdaFunction.Arn
yaml
`
$1
$1
| Memory (MB) | Cold Start | Warm Start | Cost/Million Invocations |
|---|---|---|---|
| 128 | ~800ms | ~100ms | $0.208 |
| 512 | ~600ms | ~80ms | $0.833 |
| 1024 | ~400ms | ~60ms | $1.667 |
$1
1. Multi-stage Builds
` FROM public.ecr.aws/lambda/python:3.11 as builder
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -t python/ FROM public.ecr.aws/lambda/python:3.11 as app-builder
COPY app/ app/
RUN python -m compileall app/ FROM public.ecr.aws/lambda/python:3.11-slim
COPY --from=builder python/ ${LAMBDA_TASK_ROOT}
COPY --from=app-builder app/ ${LAMBDA_TASK_ROOT}/app/
CMD [ "app.main.handler" ]
dockerfile
`Build stage for dependencies
Build stage for application code
Final stage
2. Layer Optimization
` __pycache__
*.pyc
*.pyo
*.pyd
.Python
env/
tests/
*.md
.git
.gitignore
Dockerfile*
docker-compose*
plaintext
`.dockerignore
$1
$1
` import logging
import watchtower
import boto3
from typing import Optional def setup_cloudwatch_logging(
log_group: str,
stream_name: Optional[str] = None,
log_level: int = logging.INFO
) -> None:
"""Configure CloudWatch logging with structured formatting"""
logger = logging.getLogger()
logger.setLevel(log_level)
# Remove existing handlers
logger.handlers = []
# Create CloudWatch handler
cloudwatch_handler = watchtower.CloudWatchLogHandler(
log_group=log_group,
stream_name=stream_name or 'lambda-container-logs',
boto3_client=boto3.client('logs')
)
# Create formatter
formatter = logging.Formatter(
'{"timestamp": "%(asctime)s", "level": "%(levelname)s", '
'"function": "%(funcName)s", "line": "%(lineno)d", '
'"message": %(message)s}'
)
cloudwatch_handler.setFormatter(formatter)
# Add handler
logger.addHandler(cloudwatch_handler)
python
`
$1
| Metric | CloudWatch | X-Ray | Custom |
|--------|------------|-------|--------|
| Invocations | ✓ | ✓ | Optional |
| Errors | ✓ | ✓ | Optional |
| Duration | ✓ | ✓ | Optional |
| Memory | ✓ | ✗ | Optional |
| Cold Starts | ✓ | ✓ | Optional |
| Traces | ✗ | ✓ | Optional |
| Custom Metrics | ✗ | ✗ | ✓ |
$1
$1
| Security Feature | Container Images | ZIP Packages |
|---|---|---|
| Image Scanning | Built-in ECR scanning | Manual scanning required |
| Dependency Control | Layer-based control | Package-level only |
| Image Signing | Supported | Not available |
| Access Control | ECR + IAM | IAM only |
$1
` aws ecr start-image-scan \
--repository-name lambda-container \
--image-id imageTag=latest aws ecr describe-image-scan-findings \
--repository-name lambda-container \
--image-id imageTag=latest
bash
``Scan image for vulnerabilities
Get scan results
$1
$1
| Component | Container Images | ZIP Packages | Cost Impact |
|---|---|---|---|
| Storage | ECR costs | S3 costs | Higher for containers |
| Cold Start | Longer | Shorter | Higher for containers |
| Memory Usage | Higher base | Lower base | Higher for containers |
| Transfer | ECR transfer | S3 transfer | Similar |
$1
1. Image Size Reduction
- Use multi-stage builds
- Remove unnecessary dependencies
- Implement layer caching
2. Resource Optimization
- Right-size memory allocation
- Optimize code execution time
- Use provisioned concurrency when needed
3. Monitoring and Alerts
- Set up cost alarms
- Monitor usage patterns
- Implement auto-scaling policies
$1
Lambda container images provide a powerful way to deploy complex applications while maintaining the benefits of serverless architecture. By following the best practices and optimization techniques outlined in this guide, you can effectively leverage this feature for your applications.
Key Takeaways:
$1
Why This Matters
Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.
Trade-offs to Consider
Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.
When NOT to Use This
Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.
Decision Framework
Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.