Aws
AwsIntermediate

How to Deploy AWS Lambda Functions Using Container Images

14 min read
AWSLambdaContainersServerlessDocker

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

  • Understanding Lambda container images and their benefits
  • Setting up your development environment
  • Creating and optimizing container images for Lambda
  • Deploying and managing containerized Lambda functions
  • Best practices for security and cost optimization
  • Monitoring and troubleshooting techniques
  • $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

    ``json

    {

    "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": "*"

    }

    ]

    }

    `

    $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

    `plaintext

    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

    `

    $1

    `python

    app/main.py

    import json

    import logging

    from typing import Dict, Any

    from datetime import datetime

    Configure logging

    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)

    })

    }

    `

    $1

    `plaintext

    requirements/base.txt

    requests==2.31.0

    boto3==1.34.11

    python-json-logger==2.0.7

    pydantic==2.5.2

    requirements/dev.txt

    -r base.txt

    pytest==7.4.3

    pytest-cov==4.1.0

    black==23.11.0

    mypy==1.7.1

    requirements/prod.txt

    -r base.txt

    watchtower==3.0.1

    aws-xray-sdk==2.12.1

    `

    $1

    `dockerfile

    Build stage

    FROM public.ecr.aws/lambda/python:3.11 as builder

    Copy requirements

    COPY requirements/prod.txt .

    RUN pip install --no-cache-dir -r prod.txt -t python/

    Runtime stage

    FROM public.ecr.aws/lambda/python:3.11-slim

    Copy only production dependencies

    COPY --from=builder python/ ${LAMBDA_TASK_ROOT}

    Copy application code

    COPY app/ ${LAMBDA_TASK_ROOT}/app/

    Set environment variables

    ENV PYTHONPATH=${LAMBDA_TASK_ROOT}

    ENV LOG_LEVEL=INFO

    Set the handler

    CMD [ "app.main.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

    `yaml

    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

    `

    $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

    `dockerfile

    Build stage for dependencies

    FROM public.ecr.aws/lambda/python:3.11 as builder

    COPY requirements.txt .

    RUN pip install --no-cache-dir -r requirements.txt -t python/

    Build stage for application code

    FROM public.ecr.aws/lambda/python:3.11 as app-builder

    COPY app/ app/

    RUN python -m compileall app/

    Final stage

    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" ]

    `

    2. Layer Optimization

    `plaintext

    .dockerignore

    __pycache__

    *.pyc

    *.pyo

    *.pyd

    .Python

    env/

    tests/

    *.md

    .git

    .gitignore

    Dockerfile*

    docker-compose*

    `

    $1

    $1

    `python

    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)

    `

    $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

    `bash

    Scan image for vulnerabilities

    aws ecr start-image-scan \

    --repository-name lambda-container \

    --image-id imageTag=latest

    Get scan results

    aws ecr describe-image-scan-findings \

    --repository-name lambda-container \

    --image-id imageTag=latest

    ``

    $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:

  • Use multi-stage builds for optimal image size
  • Implement proper monitoring and logging
  • Follow security best practices
  • Optimize for cost and performance
  • Maintain proper documentation and versioning
  • $1

  • [AWS Lambda Container Image Support Documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-images.html)
  • [ECR Best Practices](https://docs.aws.amazon.com/AmazonECR/latest/userguide/best-practices.html)
  • [Lambda Best Practices](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html)
  • [Container Security Best Practices](https://aws.amazon.com/blogs/containers/container-security-best-practices/)
  • [AWS Lambda Pricing](https://aws.amazon.com/lambda/pricing/)
  • 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.