DEV Community

Cover image for Prepare Application Artifacts To Be Deployed To AWS | πŸ—οΈ Build A Multi-Environment Serverless App

Prepare Application Artifacts To Be Deployed To AWS | πŸ—οΈ Build A Multi-Environment Serverless App

Exam Guide: Developer - Associate
πŸ—οΈ Domain 3: Deployment
πŸ“˜ Task 1: Prepare Application Artifacts To Be Deployed To AWS

Before you can deploy anything to AWS, you need to package it properly. This task covers Lambda deployment packaging (zip vs container), managing dependencies, structuring projects for multi-environment deployment, and using AWS AppConfig for runtime configuration.


πŸ“˜Concepts

Lambda Deployment Packaging Options

Option Max Size Build Complexity Cold Start Best For
Zip Package (inline editor) 3 MB (editor limit) None Fastest Simple functions, no dependencies
Zip Package (upload) 50 MB compressed / 250 MB uncompressed Low Fast Most Lambda functions
Zip + Lambda Layers 250 MB total (function + all layers) Medium Fast Shared dependencies across functions
Container Image 10 GB Higher Slower (first invoke) ML libraries, large dependencies, custom runtimes

πŸ’‘If a scenario is about a deployment package exceeding 250 MB, the answer is container images. If it mentions sharing dependencies across multiple functions, the answer is Lambda Layers. Zip is the default for most workloads.

Lambda Layers

Aspect Detail
What They Are Zip archives containing libraries, custom runtimes, or other dependencies
Max Layers Per Function 5
Size Limit 250 MB total (function code + all layers uncompressed)
Versioning Each publish creates an immutable version
Sharing Can be shared across functions, accounts, or made public
Path Contents extracted to /opt in the execution environment

Dependency Management Strategies

Strategy How It Works Pros Cons
Bundle In Zip Install deps into package directory, zip together Simple, self-contained Larger package, duplicated across functions
Lambda Layers Package deps as a layer, attach to functions Shared across functions, smaller deploys Layer version management, 5-layer limit
Container Image Install deps in Dockerfile Full control, large deps supported Slower cold starts, ECR management
sam build SAM resolves deps from requirements.txt automatically Easiest, handles everything Requires SAM CLI

Environment-Specific Configuration Approaches

Approach When Config Is Resolved Best For
SAM Parameters + samconfig.toml Deploy time Resource names, table names, stage-specific infra
Lambda Environment Variables Deploy time (baked into function config) Simple key-value config per environment
SSM Parameter Store Runtime (fetched by function) Config that changes without redeployment
AWS AppConfig Runtime (with gradual rollout) Feature flags, config that needs validation and rollback
Secrets Manager Runtime (fetched by function) Credentials that rotate

πŸ’‘ If the question is change configuration without redeploying, the answer is Parameter Store or AppConfig. If it says gradual rollout or validate configuration before applying, the answer is AppConfig.

AWS AppConfig Overview

Feature Detail
What It Does Deploy configuration changes independently from code, with validation and rollout strategies
Deployment Strategies AllAtOnce, Linear, Exponential
Validators JSON Schema or Lambda function to validate config before deployment
Rollback Automatic rollback if CloudWatch alarm triggers during deployment
Integration Lambda extension for caching, or direct API calls
Cost Free for configuration retrieval. Charges for feature flag evaluations

Project Structure Best Practices

A well-structured SAM project separates handlers, shared code, tests, and configuration:

my-app/
β”œβ”€β”€ template.yaml              # SAM/CloudFormation template
β”œβ”€β”€ samconfig.toml             # Per-environment deployment config
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ handlers/              # One file per Lambda function
β”‚   β”‚   β”œβ”€β”€ create_order.py
β”‚   β”‚   β”œβ”€β”€ get_order.py
β”‚   β”‚   └── process_payment.py
β”‚   └── shared/                # Shared utilities across handlers
β”‚       β”œβ”€β”€ models.py
β”‚       └── utils.py
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ unit/                  # Fast, no AWS calls
β”‚   β”‚   β”œβ”€β”€ test_create_order.py
β”‚   β”‚   └── test_get_order.py
β”‚   └── integration/           # Against deployed resources
β”‚       └── test_api.py
β”œβ”€β”€ events/                    # Sample event payloads for testing
β”‚   β”œβ”€β”€ create_order.json
β”‚   └── get_order.json
└── requirements.txt           # Python dependencies
Enter fullscreen mode Exit fullscreen mode

πŸ—οΈ Build A Multi-Environment Serverless App

Build a Multi-Environment Serverless App that demonstrates real-world packaging and deployment patterns:

  • A Lambda function packaged with dependencies via zip upload in the console
  • A container image built and pushed to ECR
  • AWS AppConfig set up with feature flags and a deployment strategy
  • A SAM project structured for multi-environment deployment
  • A samconfig.toml configured for dev, staging, and prod

Prerequisites


Part I

Package a Lambda Function with Dependencies (Zip Upload)

Create the Deployment Package Locally

Create a zip file on your local machine and upload it through the console.

⚠️ Common Windows mistake: Don't create the zip with Explorer (right-click β†’ Send to β†’ Compressed folder). That wraps everything inside a subfolder, so your handler ends up at package/lambda_function.py and Lambda throws No module named 'lambda_function'. Rather build the zip in CloudShell.

Step 01: Create a project directory and install dependencies

mkdir lambda-package && cd lambda-package

# Create requirements.txt
cat > requirements.txt << 'EOF'
requests==2.31.0
boto3>=1.28.0
EOF

# Install dependencies into a package directory
pip install -r requirements.txt -t package/

# Create your function code
cat > package/lambda_function.py << 'EOF'
import json
import requests
import boto3

def lambda_handler(event, context):
    """
    Lambda function packaged with external dependencies.
    The 'requests' library is bundled in the zip package.
    boto3 is included in the Lambda runtime, but pinning
    a version in requirements.txt ensures consistency.
    """
    # Use the bundled 'requests' library
    response = requests.get('https://httpbin.org/json')

    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': 'Function with bundled dependencies',
            'externalApiStatus': response.status_code,
            'requestsVersion': requests.__version__
        })
    }
EOF

# Create the zip package
cd package && zip -r ../deployment.zip . && cd ..
Enter fullscreen mode Exit fullscreen mode

Upload via the Console

Step 02: Open the Lambda console β†’ Create function

  • Function name: PackagedFunction
  • Runtime: Python 3.12

Click Create function

Step 03: In the Code source section, click Update β–Ό β†’ Update from a .zip file

Step 04: Upload, select your deployment.zip file

Step 05: Click Update

Test the Function

Step 06: Go to the Test tab β†’ create a test event with {}

Click Test

⚠️ Verify the response shows the requests library version and a successful API call

πŸ’‘The zip upload limit is 50 MB compressed. If your package exceeds this, upload to S3 first and reference the S3 location. The uncompressed limit is 250 MB. sam build handles all of this automatically. It installs dependencies, creates the zip, and uploads to S3.


Part II

Build And Push A Container Image To ECR

Create an ECR Repository

Step 01: Open the ECR console

Step 02: Click Create repository

  • Repository name: lambda-container-demo
  • Image Tag immutability: Mutable
  • β–Ό Image scanning settings - deprecated: Scan on push β†’ Enabled

Click Create

⚠️ Click into the repository Summary and note the URI (e.g., 123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-container-demo)

Build and Push the Image Locally

Step 03: Create a Dockerfile and function code

# Use the official AWS Lambda Python base image
FROM public.ecr.aws/lambda/python:3.12

# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt

# Copy function code
COPY app.py ${LAMBDA_TASK_ROOT}/

# Set the handler
CMD ["app.lambda_handler"]
Enter fullscreen mode Exit fullscreen mode

Step 04: Create app.py

# app.py
import json
import requests

def lambda_handler(event, context):
    """
    Lambda function running as a container image.
    Container images support up to 10 GB β€” useful for
    ML libraries, large datasets, or custom runtimes.
    """
    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': 'Hello from a container-based Lambda!',
            'runtime': 'Container image (Python 3.12)',
            'maxPackageSize': '10 GB (vs 250 MB for zip)'
        })
    }
Enter fullscreen mode Exit fullscreen mode

Step 05: Build and push

# Authenticate Docker with ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

# Build the image
docker build -t lambda-container-demo .

# Tag for ECR
docker tag lambda-container-demo:latest \
  123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-container-demo:latest

# Push to ECR
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-container-demo:latest
Enter fullscreen mode Exit fullscreen mode

Alternatively

# Authenticate Docker with ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

# Build the image β€” disable attestations so Lambda accepts the manifest
docker buildx build --provenance=false --sbom=false \
  -t 123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-container-demo:latest \
  --push .
Enter fullscreen mode Exit fullscreen mode

⚠️ Manifest Format Gotcha: If you build with a plain docker build on a recent Docker Desktop, BuildKit adds provenance and SBOM attestations that produce an OCI image index. Lambda rejects this with: "The image manifest, config or layer media type for the source image ... is not supported." Lambda only accepts Docker Image Manifest V2 Schema 2. The --provenance=false --sbom=false flags above force the compatible format. Alternatively, set DOCKER_BUILDKIT=0 before building to use the legacy builder, which always produces the compatible manifest.

If you prefer the separate tag-and-push steps (with BuildKit disabled):

# PowerShell: $env:DOCKER_BUILDKIT=0   |   CMD: set DOCKER_BUILDKIT=0
docker build -t lambda-container-demo .
docker tag lambda-container-demo:latest \
  123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-container-demo:latest
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-container-demo:latest
Enter fullscreen mode Exit fullscreen mode

Verify in the Console

Step 06: Go back to the ECR console β†’ click into lambda-container-demo

You should see your image with the latest tag

Create a Lambda Function from the Container Image

Step 07: Open the Lambda console β†’ Create function

Step 08: Select Container image

  • Function name: ContainerLambdaDemo
  • Container image URI: Click Browse images β†’ select lambda-container-demo β†’ select the latest tag

Click Create function

Step 09: Test with an empty event {}

πŸ’‘ Container-based Lambda functions use ECR for image storage. The image must implement the Lambda Runtime API. AWS provides base images for all supported runtimes (public.ecr.aws/lambda/python:3.12). You can also use arbitrary base images with the Runtime Interface Client.


Part III

Set Up AWS AppConfig with Feature Flags

Create an AppConfig Application

Step 01: Open the AppConfig console

Step 02: Click Get started

Step 03: Select configuration type

  • Configuration options: Feature flag
  • Configuration profile name: feauture-flags

Click Next

Step 04: Specify configuration data

  • Flag name: New Checkout Flow
  • Flag key: new-checkout-flow
  • Flag description - Optional: Enable the redesigned checkout experience
  • Variants: Basic flag
  • Enabled value: Toggle **ON**

Click Next

Step 05: Review and save
Save to application

  • Application name: orders-service
  • Description: Feature flags and configuration for the orders service

Click Save and continue to deploy

Step 06: Start deployment

Environment: Click Create environment

Step 07: Create environment

  • Name: production
  • Description: Production environment

Click Create environment

Add A Feature Flag

Step 08: Click Add flag

  • Flag name: Dark Mode
  • Flag key: dark-mode
  • Description: Enable dark mode UI
  • Variants: Basic flag
  • Enabled value: Toggle **OFF**

Click Save new version

Create a Deployment Strategy

Step 09: Deployment strategies β†’ Create deployment strategy

  • Name: GradualRollout
  • Description: Deploy over 10 minutes with bake time
  • Deployment type: Linear β–Ό
  • Step percentage: 20
  • Deployment time: 10 minutes
  • Bake time: 5 minutes

Click Create deployment strategy

Deploy the Configuration

Step 10: Go to your orders-service application β†’ production environment

Click Start deployment

Step 11: Start deployment

  • Configuration profile: feature-flags
  • Hosted configuration version: (latest)
  • Deployment strategy: GradualRollout

Click Start deployment

⚠️ Watch the deployment progress. It rolls out 20% at a time over 10 minutes

πŸ’‘AppConfig deployment strategies control how fast configuration changes roll out. If a CloudWatch alarm fires during deployment, AppConfig automatically rolls back. This is safer than changing a Parameter Store value directly, which takes effect immediately for all callers.


Part IV

Structure a SAM Project for Multi-Environment Deployment

Step 01: Initialize the Project

sam init --runtime python3.12 --name multi-env-app --app-template hello-world
cd multi-env-app
Enter fullscreen mode Exit fullscreen mode

Step 02: Create the Handler Files

⚠️ sam init creates a hello_world/ folder, but our template uses a src/handlers/ structure with two functions. Create these files (delete the generated hello_world/ folder afterward. It's no longer referenced):

Step 03: Create src/handlers/requirements.txt:

boto3
Enter fullscreen mode Exit fullscreen mode

Step 04: Create src/handlers/create_order.py:

import json
import os
import boto3
import uuid

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])

def lambda_handler(event, context):
    body = json.loads(event.get('body', '{}'))
    order_id = str(uuid.uuid4())[:8].upper()

    table.put_item(Item={
        'PK': f'ORDER#{order_id}',
        'SK': 'METADATA',
        'customerId': body.get('customerId', 'unknown'),
        'status': 'created'
    })

    return {
        'statusCode': 201,
        'body': json.dumps({'orderId': f'ORD-{order_id}', 'status': 'created'})
    }
Enter fullscreen mode Exit fullscreen mode

Step 05: Create src/handlers/get_order.py:

import json
import os
import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])

def lambda_handler(event, context):
    order_id = event['pathParameters']['orderId']

    response = table.get_item(Key={'PK': f'ORDER#{order_id}', 'SK': 'METADATA'})
    item = response.get('Item')

    if not item:
        return {'statusCode': 404, 'body': json.dumps({'error': 'Order not found'})}

    return {'statusCode': 200, 'body': json.dumps(item, default=str)}
Enter fullscreen mode Exit fullscreen mode

⚠️ The template's CodeUri: src/handlers/ and Handler: create_order.lambda_handler must point to real files. If src/handlers/ doesn't exist or is missing requirements.txt, sam build fails with "source ... does not exist" or "requirements.txt file not found." The CodeUri is the folder SAM packages; the Handler is filename.function_name relative to that folder.

Step 06: Configure the SAM Template with Parameters

Replace the contents of template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Multi-environment serverless application

Parameters:
  Stage:
    Type: String
    Default: dev
    AllowedValues: [dev, staging, prod]
  TableName:
    Type: String
    Default: orders

Globals:
  Function:
    Runtime: python3.12
    Timeout: 30
    Environment:
      Variables:
        STAGE: !Ref Stage
        TABLE_NAME: !Sub "${Stage}-${TableName}"

Resources:
  OrdersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub "${Stage}-${TableName}"
      BillingMode: PAY_PER_REQUEST
      KeySchema:
        - AttributeName: PK
          KeyType: HASH
        - AttributeName: SK
          KeyType: RANGE
      AttributeDefinitions:
        - AttributeName: PK
          AttributeType: S
        - AttributeName: SK
          AttributeType: S

  CreateOrderFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/handlers/
      Handler: create_order.lambda_handler
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref OrdersTable
      Events:
        CreateOrder:
          Type: Api
          Properties:
            Path: /orders
            Method: post

  GetOrderFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/handlers/
      Handler: get_order.lambda_handler
      Policies:
        - DynamoDBReadPolicy:
            TableName: !Ref OrdersTable
      Events:
        GetOrder:
          Type: Api
          Properties:
            Path: /orders/{orderId}
            Method: get

Outputs:
  ApiUrl:
    Description: API Gateway endpoint URL
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/"
Enter fullscreen mode Exit fullscreen mode

Step 07: Configure samconfig.toml for Multiple Environments

# samconfig.toml β€” one section per environment
# The version key is REQUIRED β€” SAM CLI won't parse the file without it
version = 0.1

[default.deploy.parameters]
stack_name = "multi-env-app-dev"
resolve_s3 = true
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Stage=dev"
confirm_changeset = false

[staging.deploy.parameters]
stack_name = "multi-env-app-staging"
resolve_s3 = true
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Stage=staging"
confirm_changeset = false

[prod.deploy.parameters]
stack_name = "multi-env-app-prod"
resolve_s3 = true
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Stage=prod"
confirm_changeset = true
Enter fullscreen mode Exit fullscreen mode

Step 08: Deploy to Different Environments

# Build once β€” use --use-container to build inside a Lambda-compatible
# Docker image (needs Docker running, but no local Python required)
sam build --use-container

# Or, if you have Python 3.12 installed and on your PATH:
# sam build

# Deploy to dev (default config)
sam deploy

# Deploy to staging
sam deploy --config-env staging

# Deploy to prod (will prompt for changeset confirmation)
sam deploy --config-env prod
Enter fullscreen mode Exit fullscreen mode

⚠️ sam build can't find Python? If you see "Binary validation failed for python ... did you have python for runtime: python3.12 on your PATH?", it means Python 3.12 isn't installed locally (the WindowsApps\python.EXE entries are Microsoft Store stubs, not a real install). Use sam build --use-container to build inside a Docker container that already has the correct runtime, or install Python 3.12 from python.org and check "Add python.exe to PATH" during setup. CloudShell also has Python 3.12 and SAM pre-installed.

Each deployment creates isolated resources: dev-orders table, staging-orders table, prod-orders table.

πŸ’‘ samconfig.toml uses config environments (sections like [staging.deploy.parameters]) to manage per-environment settings. The --config-env flag selects which section to use. Setting confirm_changeset = true for production forces you to review changes before applying them.


πŸ—οΈ What You Built | πŸ“˜ Exam Concepts Recap

What You Built Exam Concept
Bundled requests into a zip and uploaded via console Zip packaging with dependencies, 50/250 MB limits
Built a Dockerfile and pushed to ECR Container image packaging (up to 10 GB)
Created a Lambda from a container image When to use containers vs zip
Created an AppConfig application and environment Runtime configuration management
Added feature flags with enabled/disabled toggles Feature flag pattern for decoupling release from deploy
Created a Linear deployment strategy with bake time Gradual config rollout with automatic rollback
Used SAM parameters and !Sub for resource names Environment-specific infrastructure
Configured samconfig.toml for dev/staging/prod Multi-environment deployment with --config-env
Set confirm_changeset = true for prod Forcing review before production changes

⚠️ Clean Up Protocol

  1. Lambda β†’ Delete PackagedFunction and ContainerLambdaDemo
  2. ECR β†’ Delete the lambda-container-demo repository (and all images)
  3. AppConfig β†’ Delete the deployment, then the configuration profile, environment, and application (in that order)
  4. CloudFormation β†’ Delete any SAM-deployed stacks (multi-env-app-dev, etc.)
  5. IAM β†’ Delete Lambda execution roles
  6. CloudWatch β†’ Delete log groups
  7. S3 β†’ Delete any SAM deployment buckets (prefixed with aws-sam-cli-managed-default)

Key Takeaways

  1. Zip packages: 50 MB compressed / 250 MB uncompressed. Container images: up to 10 GB. Use containers for large dependencies.
  2. Lambda Layers share dependencies across functions. Up to 5 layers, 250 MB total uncompressed.
  3. sam build resolves dependencies automatically. sam deploy packages and deploys. sam deploy --guided for first-time setup.
  4. AppConfig deploys configuration independently from code, with validation, gradual rollout, and automatic rollback.
  5. samconfig.toml manages multi-environment deployment configs. Use --config-env to select the environment.
  6. ECR stores container images. Use public.ecr.aws/lambda/ base images for Lambda containers.
  7. Use CloudFormation parameters and !Sub for environment-specific resource names.
  8. SAM policy templates (DynamoDBCrudPolicy, S3ReadPolicy) enforce least privilege without writing full IAM policies.

Additional Resources


πŸ—οΈ

Top comments (1)

Collapse
 
topstar_ai profile image
Luis

This is a solid breakdown of something that often gets underestimated in serverless architectures: artifact preparation across multiple environments is where most deployment complexity actually lives.

The β€œbuild once, deploy everywhere” idea sounds simple, but in practice multi-environment serverless setups introduce a lot of hidden variables:

environment-specific configuration drift (dev vs staging vs prod)
IAM and permission boundaries per stage
dependency/version consistency across builds
and subtle differences in event sources and triggers

What I like about this approach is the emphasis on treating artifacts as immutable deployment units, rather than rebuilding logic per environment. That alone reduces a lot of inconsistencies.

In real-world AWS setups, I’ve found the biggest win comes from standardizing:

build pipeline (single source of truth for artifacts)
parameterized deployment templates (instead of branching logic)
and strict separation of state vs configuration

One thing I’d be curious about is how you handle rollback strategies across environmentsβ€”especially when multiple services depend on the same artifact version.

Overall, this is exactly the kind of discipline that separates β€œworks in dev” serverless apps from production-grade systems.