Part 5: Deployment and Best Practices

Production-Ready Lambda Functions

After deploying dozens of Lambda functions to production, I've learned that getting code to work locally is only the beginning. This final part shares the lessons that transformed my functions from working prototypes to reliable production services.

CI/CD Pipeline for Lambda

Here's the deployment pipeline I use for all my Lambda projects:

Complete CI/CD Flow

spinner

GitHub Actions Workflow

Create .github/workflows/deploy.yml:

name: Deploy Lambda Function

on:
  push:
    branches:
      - main
      - develop
  pull_request:
    branches:
      - main

env:
  AWS_REGION: us-east-1
  PYTHON_VERSION: '3.12'

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      
      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt
      
      - name: Run linters
        run: |
          # Black for code formatting
          black --check src/
          
          # Flake8 for style guide
          flake8 src/ --max-line-length=100
          
          # MyPy for type checking
          mypy src/
      
      - name: Run unit tests
        run: |
          pytest tests/ -v --cov=src --cov-report=xml
      
      - name: Security scan
        run: |
          # Bandit for security issues
          bandit -r src/ -f json -o bandit-report.json
          
          # Safety for dependency vulnerabilities
          safety check --json
      
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage.xml

  deploy-test:
    needs: test
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    environment: test
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      
      - name: Set up SAM CLI
        uses: aws-actions/setup-sam@v2
      
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}
      
      - name: Build SAM application
        run: sam build --use-container
      
      - name: Deploy to test
        run: |
          sam deploy \
            --stack-name user-registration-test \
            --s3-bucket ${{ secrets.SAM_BUCKET }} \
            --s3-prefix test \
            --capabilities CAPABILITY_IAM \
            --parameter-overrides Environment=test \
            --no-fail-on-empty-changeset
      
      - name: Run integration tests
        run: |
          export API_ENDPOINT=$(aws cloudformation describe-stacks \
            --stack-name user-registration-test \
            --query 'Stacks[0].Outputs[?OutputKey==`ApiEndpoint`].OutputValue' \
            --output text)
          
          pytest tests/integration/ -v --api-endpoint=$API_ENDPOINT

  deploy-prod:
    needs: deploy-test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      
      - name: Set up SAM CLI
        uses: aws-actions/setup-sam@v2
      
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}
      
      - name: Build SAM application
        run: sam build --use-container
      
      - name: Deploy to production
        run: |
          sam deploy \
            --stack-name user-registration-prod \
            --s3-bucket ${{ secrets.SAM_BUCKET }} \
            --s3-prefix prod \
            --capabilities CAPABILITY_IAM \
            --parameter-overrides Environment=production \
            --no-fail-on-empty-changeset
      
      - name: Create deployment marker
        run: |
          aws cloudwatch put-metric-data \
            --namespace CustomMetrics/Deployments \
            --metric-name Deployment \
            --value 1 \
            --dimensions Function=UserRegistration,Environment=production
      
      - name: Smoke tests
        run: |
          export API_ENDPOINT=$(aws cloudformation describe-stacks \
            --stack-name user-registration-prod \
            --query 'Stacks[0].Outputs[?OutputKey==`ApiEndpoint`].OutputValue' \
            --output text)
          
          # Basic health check
          curl -f $API_ENDPOINT/health || exit 1

Infrastructure as Code Best Practices

Multi-Environment SAM Template

Create template.yaml:

Monitoring and Observability

CloudWatch Dashboard

spinner

Custom Metrics with Lambda Powertools

Install dependencies in requirements.txt:

Update src/handler.py:

Performance Optimization

Cold Start Reduction

Strategy 1: Provisioned Concurrency

Strategy 2: Lambda SnapStart (for Java)

For Python, focus on:

  • Minimize dependencies

  • Use Lambda Layers for common libraries

  • Optimize initialization code

Strategy 3: Lambda Layers

Create layers/requirements.txt:

Update template.yaml:

Memory Optimization

From my testing, here's the performance data:

My recommendation: Start with 512-1024 MB, then optimize based on CloudWatch Insights.

Query CloudWatch Insights

Security Best Practices

Secrets Management

Never hardcode secrets! Use AWS Secrets Manager:

IAM Permissions

Follow least privilege:

Cost Optimization

My Cost Optimization Checklist

Right-Size Memory: Test and find optimal memory setting ✅ Use Reserved Concurrency: Prevent runaway costs ✅ Enable S3 Intelligent Tiering: For stored artifacts ✅ Set DynamoDB to On-Demand: For variable workloads ✅ Use CloudWatch Logs Retention: Don't keep logs forever ✅ Implement Circuit Breakers: Prevent cascading failures ✅ Monitor with Cost Anomaly Detection: Get alerted to spikes

Cost Monitoring Dashboard

Testing Strategy

Unit Tests

Create tests/test_handler.py:

Integration Tests

Create tests/integration/test_api.py:

Key Takeaways

From deploying Lambda functions to production:

  1. Automate Everything: CI/CD is non-negotiable

  2. Infrastructure as Code: SAM/CloudFormation for reproducibility

  3. Monitor Proactively: Metrics, logs, and traces

  4. Optimize Costs: Right-size and monitor spending

  5. Security First: Least privilege, secrets management

  6. Test Thoroughly: Unit, integration, and load tests

  7. Plan for Failure: DLQs, retries, circuit breakers

Series Conclusion

Throughout this Serverless 101 series, we've covered:

  • Part 1: Serverless fundamentals and when to use Lambda

  • Part 2: Lambda execution model and Python runtime

  • Part 3: Building a production-ready Lambda function

  • Part 4: Event-driven architecture patterns

  • Part 5: Deployment, monitoring, and best practices

Your Serverless Journey

You now have the knowledge to:

  • ✅ Design event-driven serverless applications

  • ✅ Build and deploy Lambda functions with Python

  • ✅ Implement CI/CD pipelines

  • ✅ Monitor and optimize production functions

  • ✅ Follow security and cost best practices

Next Steps

Continue learning:

  • Explore Step Functions for orchestration

  • Learn about Lambda@Edge for CDN computing

  • Study serverless data processing with Kinesis

  • Investigate serverless ML inference

Additional Resources


This series represents years of hands-on experience building and operating serverless applications in production. I hope it accelerates your serverless journey!

Happy building! 🚀

Last updated