How to Set Up AWS Lambda: 12 Steps, 90 Min [2026]

AWS Lambda now runs trillions of invocations a month across millions of AWS accounts, and it remains the fastest on-ramp into serverless computing for developers who don’t want to manage a single server. If you’ve typed “aws lambda tutorial” into a search bar this week, you’re one of roughly 12,100 people a month doing the same thing in the US alone, based on current keyword tracking data. This guide walks through building a real serverless application from scratch: a Lambda function wired to Amazon API Gateway, triggered by an S3 upload, storing results in DynamoDB, monitored through CloudWatch, and deployed with the AWS SAM CLI. By the end you’ll have a working REST API running on infrastructure you never had to patch, reboot, or scale manually.

This is a hands-on build, not a conceptual overview. You’ll create an AWS account (or use an existing one), install the tools, write real handler code in Python, deploy it, break it on purpose to learn how errors surface, and fix it. The whole thing runs inside the AWS Free Tier for testing purposes, so you can follow along without a credit card bill you didn’t expect.

Google · Preferred Sources

Don't miss new tech stories on Google

Add Tech Insider once in the Google app and our stories appear in your news suggestions.

Add Now

What Is AWS Lambda and Why It Matters in 2026

AWS Lambda is a compute service that runs your code in response to events without requiring you to provision or manage servers. You upload a function, attach it to a trigger (an HTTP request, a file upload, a database change, a scheduled timer), and AWS handles the provisioning, scaling, patching, and teardown of the underlying compute. You pay only for the milliseconds your code actually executes, not for idle capacity sitting around waiting for traffic.

The pitch hasn’t changed much since Lambda launched in 2014, but the runtime landscape has moved fast. According to AWS’s current documentation, the console now defaults new function creation to modern managed runtimes, and AWS has been steadily retiring older language versions on a rolling deprecation schedule tied to each language’s own end-of-life date. If you’re following an older tutorial that references Node.js 12 or Python 3.7, that guidance is stale — those runtimes are long past AWS’s deprecation cutoff and functions created with them will fail to deploy in the console today.

What makes Lambda worth learning specifically in 2026 is how far the ecosystem around it has matured. API Gateway, EventBridge, Step Functions, and the SAM CLI now form a genuinely productive serverless stack, and AWS’s own workshop and getting-started documentation was refreshed as recently as this month, according to AWS’s public documentation timestamps. That’s a signal AWS is still actively pushing Lambda as the default entry point for new serverless workloads, not a legacy service being left to rot.

Prerequisites: What You Need Before You Start

Gather these before Step 1. Skipping this section is the single biggest reason people get stuck halfway through a Lambda tutorial and give up.

RequirementVersion / DetailNotes
AWS accountActive, billing enabledFree Tier covers everything in this tutorial for a new or low-usage account
AWS CLIv2, latest releaseRequired for SAM deploys and scripted testing
AWS SAM CLILatest releaseUsed to build, package, and deploy the serverless stack locally
Python3.13 or 3.12Matches Lambda’s current supported managed runtimes; avoid anything below 3.11
Node.js22.x (optional)Only needed if you follow the Node.js code samples instead of Python
DockerDesktop, latestNeeded for `sam local invoke` to emulate the Lambda execution environment
IAM permissionsProgrammatic accessAn IAM user or role with Lambda, API Gateway, S3, DynamoDB, and CloudWatch permissions
Code editorVS Code or similarAWS Toolkit extension is optional but speeds up local debugging

You do not need prior serverless experience, but basic familiarity with the command line and a scripting language (Python or JavaScript) will make this go faster. Budget 90 minutes for a first pass through every step, including the AWS console back-and-forth that always eats more time than you plan for.

Step 1: Create Your AWS Account and Root User Safeguards

If you already have an AWS account, skip to Step 2. If not, sign up at the AWS console sign-up flow and enable billing. The moment your account is live, do two things before anything else: enable MFA on the root user, and create an IAM user with programmatic access instead of using root credentials for daily work. AWS’s own security guidance is blunt about this — the root user should be used only for account-level tasks like changing your support plan, and never for day-to-day development.

Create an IAM user (or better, an IAM Identity Center permission set if your organization uses it) with the `AWSLambda_FullAccess`, `AmazonAPIGatewayAdministrator`, `AmazonS3FullAccess`, and `AmazonDynamoDBFullAccess` managed policies attached for this tutorial. In production you’d scope these down to least-privilege custom policies, but broad managed policies are fine for a learning environment you’ll tear down afterward.

Step 2: Install and Configure the AWS CLI

Install AWS CLI v2 for your OS, then configure it with the access key and secret from the IAM user you created in Step 1.

# macOS
brew install awscli

# Windows (PowerShell, run as admin)
msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi

# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# Configure credentials
aws configure
# AWS Access Key ID: [paste your key]
# AWS Secret Access Key: [paste your secret]
# Default region name: us-east-1
# Default output format: json

# Verify
aws sts get-caller-identity

The `get-caller-identity` call should return your account number, user ARN, and user ID. If it errors out, your credentials weren’t saved correctly — re-run `aws configure` and double-check you copied the secret key without a trailing space.

Step 3: Install the AWS SAM CLI and Docker

The Serverless Application Model (SAM) CLI is AWS’s own framework for building, testing, and deploying Lambda applications as infrastructure-as-code, rather than clicking through the console every time you change a line of code. It also lets you invoke functions locally inside a Docker container that mirrors the real Lambda execution environment, which is the fastest way to catch bugs before they cost you a deploy cycle.

# macOS
brew tap aws/tap
brew install aws-sam-cli

# Windows: download the SAM CLI MSI installer from AWS docs and run it

# Linux
wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
sudo ./sam-installation/install

# Verify
sam --version
docker --version

Make sure Docker Desktop is actually running before you try `sam local invoke` later — this is the number one cause of confusing SAM errors that have nothing to do with your code.

Step 4: Create Your First Lambda Function From the Console

Before touching infrastructure-as-code, build one function by hand in the console so you understand what SAM is automating later. Open the Functions page of the Lambda console and choose Create function, then Author from scratch. AWS’s own getting-started documentation, updated as recently as this week, walks through exactly this flow.

Set the function name to `myFirstLambdaFunction`, choose Python 3.13 as the runtime, and leave the default execution role (AWS creates one automatically with basic CloudWatch Logs permissions). Click Create function. Replace the default handler code with this:

import json

def lambda_handler(event, context):
    print("Received event:", json.dumps(event))
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "Hello from Lambda!",
            "requestId": context.aws_request_id
        })
    }

Click Deploy to save the code, then click Test, create a new test event using the default “hello-world” template, and run it. You should see a 200 response with the JSON body in the execution results panel, along with a duration in milliseconds and billed duration — the two numbers that determine your actual cost.

Step 5: Understand the Lambda Execution Model

Before building anything more complex, it’s worth understanding what just happened under the hood, because it explains almost every weird bug you’ll hit later. According to AWS’s serverless developer guide, processing an event with Lambda breaks down into three conceptual steps: you configure the entry point (the handler), the Lambda service initializes the function and its runtime environment, then it invokes the handler with an event object and a context object.

That “initializes the function” step is where cold starts come from. The first invocation after a period of inactivity has to spin up a fresh execution environment, load your code, and run any code outside the handler function before it can process the event — this is the cold start penalty you’ll see referenced constantly in serverless discussions. Subsequent invocations within a short window reuse that warm environment, which is why the second and third test runs in the console will almost always show a lower duration than the first.

ConceptWhat It MeansWhy It Matters
HandlerThe function AWS calls when your Lambda is invokedMust match the format `filename.function_name` in your config
Event objectJSON payload describing what triggered the invocationStructure varies by trigger source (API Gateway, S3, DynamoDB, etc.)
Context objectRuntime metadata: request ID, memory limit, time remainingUse `context.get_remaining_time_in_millis()` to avoid timeouts
Cold startLatency from spinning up a fresh execution environmentTypically 100ms-1s depending on runtime and package size
Execution roleIAM role Lambda assumes to run your codeControls what AWS resources your function can touch
ConcurrencyNumber of simultaneous executionsDefault account limit is 1,000 unless you request an increase

Step 6: Set Up Your Local SAM Project Structure

Now switch from console-driven development to infrastructure-as-code, which is how you’ll actually want to manage anything beyond a toy function. Initialize a new SAM project:

sam init --runtime python3.13 --name lambda-s3-api-tutorial --app-template hello-world

cd lambda-s3-api-tutorial
tree .
# lambda-s3-api-tutorial/
# ├── hello_world/
# │   ├── app.py
# │   └── requirements.txt
# ├── tests/
# ├── template.yaml
# ├── samconfig.toml
# └── README.md

The `template.yaml` file is the heart of a SAM project — it’s a CloudFormation template with SAM-specific shorthand that defines every resource in your stack: functions, API Gateway routes, S3 buckets, DynamoDB tables, IAM permissions, and how they connect. Everything you clicked through manually in Step 4 can be expressed here as declarative config that you can version-control, review, and redeploy identically every time.

Step 7: Build a REST API With API Gateway and DynamoDB

Now build something closer to a real application: a REST API backed by Lambda that writes and reads items from a DynamoDB table. AWS’s own tutorial for this exact pattern outlines four stages: create and configure a Lambda function to perform operations on a DynamoDB table, create a REST API in API Gateway to connect to that function, create the DynamoDB table and test it in the console, then deploy the API and test the full setup with curl. That’s the shape we’re following here, wired through SAM instead of manual console clicks.

Replace `template.yaml` with the following:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Lambda + API Gateway + DynamoDB tutorial stack

Globals:
  Function:
    Timeout: 10
    MemorySize: 256
    Runtime: python3.13

Resources:
  ItemsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: tutorial-items
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH

  ItemsFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: items_function/
      Handler: app.lambda_handler
      Environment:
        Variables:
          TABLE_NAME: !Ref ItemsTable
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref ItemsTable
      Events:
        GetItems:
          Type: Api
          Properties:
            Path: /items
            Method: get
        CreateItem:
          Type: Api
          Properties:
            Path: /items
            Method: post

Outputs:
  ApiEndpoint:
    Description: API Gateway endpoint URL
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/items"

Create `items_function/app.py` with the handler logic:

import json
import os
import uuid
import boto3

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

def lambda_handler(event, context):
    method = event.get("httpMethod", "GET")

    if method == "POST":
        body = json.loads(event.get("body") or "{}")
        item_id = str(uuid.uuid4())
        table.put_item(Item={"id": item_id, "name": body.get("name", "unnamed")})
        return {
            "statusCode": 201,
            "body": json.dumps({"id": item_id, "status": "created"})
        }

    response = table.scan()
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps(response.get("Items", []))
    }

Add `boto3` to `items_function/requirements.txt` (it’s included in the Lambda Python runtime by default, but pinning it locally keeps `sam build` consistent). This function branches on HTTP method: a POST creates an item with a generated UUID, a GET scans and returns every item in the table.

Step 8: Build, Test Locally, and Deploy the Stack

Build the SAM application, which resolves dependencies and packages your code:

sam build

# Test locally with a simulated API Gateway event, no AWS resources needed
sam local invoke ItemsFunction --event events/get-event.json

# Or spin up a local API Gateway emulator on port 3000
sam local start-api
curl http://127.0.0.1:3000/items

Once local testing looks good, deploy to AWS with guided mode, which walks you through stack naming, region confirmation, and IAM capability acknowledgment:

sam deploy --guided

# Answer the prompts:
# Stack Name: lambda-s3-api-tutorial
# AWS Region: us-east-1
# Confirm changes before deploy: Y
# Allow SAM CLI IAM role creation: Y
# Save arguments to configuration file: Y

# Subsequent deploys are simpler
sam deploy

When the deploy finishes, SAM prints the `ApiEndpoint` output from your template. Test it with curl exactly like AWS’s own tutorial recommends:

curl -X POST https://YOUR-API-ID.execute-api.us-east-1.amazonaws.com/Prod/items \
  -H "Content-Type: application/json" \
  -d '{"name": "first tutorial item"}'

curl https://YOUR-API-ID.execute-api.us-east-1.amazonaws.com/Prod/items

The first call returns a 201 with a generated ID. The second call returns the full array of items currently sitting in your DynamoDB table, proving the whole chain — API Gateway, Lambda, DynamoDB — is wired correctly end to end.

Step 9: Add an S3 Trigger for Event-Driven Processing

REST APIs are one trigger pattern; event-driven file processing is another common one. AWS’s getting-started guidance specifically calls out setting up an S3 event trigger so a Lambda function fires automatically when an object lands in a bucket — this is the pattern behind thumbnail generation, log processing, and document ingestion pipelines. Add this to your `template.yaml`:

  UploadsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "tutorial-uploads-${AWS::AccountId}"

  ProcessUploadFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: process_upload/
      Handler: app.lambda_handler
      Policies:
        - S3ReadPolicy:
            BucketName: !Sub "tutorial-uploads-${AWS::AccountId}"
      Events:
        S3Upload:
          Type: S3
          Properties:
            Bucket: !Ref UploadsBucket
            Events: s3:ObjectCreated:*
# process_upload/app.py
import json
import urllib.parse

def lambda_handler(event, context):
    for record in event["Records"]:
        bucket = record["s3"]["bucket"]["name"]
        key = urllib.parse.unquote_plus(record["s3"]["object"]["key"])
        size = record["s3"]["object"]["size"]
        print(f"New object: s3://{bucket}/{key} ({size} bytes)")
    return {"statusCode": 200, "body": json.dumps({"processed": len(event["Records"])})}

Run `sam build && sam deploy`, then upload a test file to confirm the trigger fires:

echo "test upload" > test.txt
aws s3 cp test.txt s3://tutorial-uploads-YOUR_ACCOUNT_ID/test.txt

# Check logs
sam logs -n ProcessUploadFunction --stack-name lambda-s3-api-tutorial --tail

Step 10: Monitor With CloudWatch Logs and Metrics

Every Lambda invocation automatically streams logs to CloudWatch as long as your execution role includes basic logging permissions (SAM adds this by default). You’ll live in CloudWatch Logs constantly once your functions are past the toy-example stage, so learn the key commands now instead of clicking through the console every time.

# Tail logs for a specific function in real time
sam logs -n ItemsFunction --stack-name lambda-s3-api-tutorial --tail

# Filter for errors only
aws logs filter-log-events \
  --log-group-name /aws/lambda/lambda-s3-api-tutorial-ItemsFunction \
  --filter-pattern "ERROR"

# Check invocation, error, and duration metrics
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Errors \
  --dimensions Name=FunctionName,Value=lambda-s3-api-tutorial-ItemsFunction \
  --start-time 2026-08-17T00:00:00Z \
  --end-time 2026-08-18T00:00:00Z \
  --period 3600 \
  --statistics Sum

Set a CloudWatch alarm on the Errors metric for anything you deploy beyond a personal test project. A function that starts silently failing at 2 a.m. because of an expired API key somewhere downstream is a much worse discovery at 9 a.m. the next day than an alert the moment it happened.

Step 11: Understand Lambda Pricing and the Free Tier

Lambda billing is based on two dimensions: the number of requests and the compute time your function consumes, measured in GB-seconds (memory allocated multiplied by execution duration). AWS’s Free Tier includes a fixed monthly allowance of requests and compute time at no charge, which is generous enough that a personal project or this entire tutorial will realistically cost nothing.

Pricing DimensionDetailPractical Impact
RequestsBilled per million requestsFree Tier covers a substantial monthly allowance before charges begin
DurationBilled per GB-second, rounded to the nearest 1msLower memory + faster code = lower bill, but more memory can sometimes finish faster and cost less overall
Memory range128 MB to 10,240 MB configurableCPU scales proportionally with memory allocation
Max timeout900 seconds (15 minutes)Long-running batch jobs may need Step Functions or Fargate instead
API Gateway REST APIBilled per million API calls plus data transferSeparate from Lambda’s own billing, adds up on high-traffic APIs
DynamoDB on-demandBilled per read/write request unitPay-per-request billing mode used in this tutorial avoids capacity planning

The classic beginner mistake is over-allocating memory “just in case.” Start at 256 MB for lightweight API handlers and only raise it if CloudWatch metrics show your function is CPU-starved or hitting timeout errors. For CPU-bound workloads like image processing, more memory often reduces total cost even though the per-GB-second rate is technically higher, because the function finishes so much faster.

Step 12: Secure Your Function With Least-Privilege IAM

The `DynamoDBCrudPolicy` and `S3ReadPolicy` shortcuts used earlier in this tutorial are SAM policy templates — they generate scoped IAM policies automatically instead of forcing you to hand-write JSON. That’s the right default for learning, but before anything touches production, audit exactly what each function’s execution role can do.

# List the execution role attached to a function
aws lambda get-function --function-name lambda-s3-api-tutorial-ItemsFunction \
  --query 'Configuration.Role'

# List policies attached to that role
aws iam list-attached-role-policies --role-name YOUR_ROLE_NAME
aws iam list-role-policies --role-name YOUR_ROLE_NAME

Never attach `AdministratorAccess` or overly broad wildcard policies to a Lambda execution role, even temporarily “to test something.” A compromised function with admin rights is a full account compromise. Scope every role to the specific table, bucket, or queue it actually needs, and use resource-level ARNs instead of `*` wherever the service supports it.

What AWS’s Own Documentation Actually Says

It’s worth reading the source material directly rather than relying on secondhand summaries, since AWS updates these pages frequently and third-party tutorials go stale fast. Here’s what AWS’s current documentation says, word for word, about the exact workflow covered in this guide.

On creating your first function, AWS’s Lambda developer guide states: “To create a Hello world Lambda function with the console: Open the Functions page of the Lambda console. Choose Create function. Select Author from scratch.” That’s the exact three-click path this tutorial’s Step 4 walks through.

On the four-stage build used in Step 7, AWS’s API Gateway integration tutorial describes the flow this way: “To complete this tutorial, you go through the following stages: 1. Create and configure a Lambda function in Python or Node.js to perform operations on a DynamoDB table. 2. Create a REST API in API Gateway to connect to your Lambda function. 3. Create a DynamoDB table and test it with your Lambda function in the console. 4. Deploy your API and test the full setup using curl in a terminal.”

On wiring an S3 trigger, AWS’s own Lambda getting-started page instructs: “Finally, set up an event trigger for Amazon S3 that will invoke your Lambda function when an event occurs.” That’s precisely the `s3:ObjectCreated:*` event source configured in Step 9’s `template.yaml`.

On locating the console entry point, AWS’s serverless code tutorial notes: “In the top navigation bar, search for Lambda and open the AWS Lambda Console” — a small detail, but the exact navigation path AWS expects a first-time user to follow.

And for teams who reach for the Serverless Framework instead of SAM, its documentation explains the equivalent config structure: “All of the Lambda functions in your serverless service can be found in serverless.yml under the functions property.” Functionally this plays the same role as the `Resources` block in this tutorial’s `template.yaml` — different tooling, same underlying CloudFormation-driven deployment model.

Real-World Use Cases Beyond This Tutorial

The REST API and S3 trigger patterns covered here are the two most common Lambda entry points, but they’re far from the only ones. Once you’re comfortable with the basic build, these are the patterns worth exploring next.

  • Scheduled jobs. An EventBridge Scheduler rule can invoke a Lambda function on a cron-style schedule — nightly reports, cleanup jobs, or periodic health checks without a dedicated server running 24/7.
  • Stream processing. Lambda can consume records from Kinesis Data Streams or a DynamoDB Stream in near real time, useful for fraud detection, activity feeds, or change-data-capture pipelines.
  • Chatbot and voice backends. Lex and Alexa Skills both use Lambda as their fulfillment layer, translating parsed user intent into business logic.
  • Image and video processing. The S3 trigger pattern from Step 9 is the backbone of most thumbnail generation, format conversion, and content moderation pipelines built on AWS.
  • Authentication hooks. Amazon Cognito supports Lambda triggers at nearly every stage of the sign-up and sign-in flow, letting you customize validation, enrichment, and post-confirmation logic.
  • CI/CD automation. Lambda functions triggered by CodePipeline or GitHub webhooks can run custom validation, notify a Slack channel, or kick off downstream deployments.

Testing Your Lambda Functions With pytest

Console testing and `sam local invoke` cover manual checks, but any function you intend to maintain needs real unit tests that run in CI before every deploy. Since the handler in Step 7 talks to DynamoDB through `boto3`, mock that client rather than hitting a real table during tests.

# tests/unit/test_items_function.py
import json
import os
import boto3
from moto import mock_aws
import pytest

os.environ["TABLE_NAME"] = "tutorial-items-test"

@mock_aws
def test_post_creates_item():
    dynamodb = boto3.client("dynamodb", region_name="us-east-1")
    dynamodb.create_table(
        TableName="tutorial-items-test",
        AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
        KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
        BillingMode="PAY_PER_REQUEST",
    )

    from items_function import app

    event = {"httpMethod": "POST", "body": json.dumps({"name": "unit test item"})}
    response = app.lambda_handler(event, None)

    assert response["statusCode"] == 201
    body = json.loads(response["body"])
    assert body["status"] == "created"

@mock_aws
def test_get_returns_empty_list_when_table_empty():
    dynamodb = boto3.client("dynamodb", region_name="us-east-1")
    dynamodb.create_table(
        TableName="tutorial-items-test",
        AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
        KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
        BillingMode="PAY_PER_REQUEST",
    )

    from items_function import app

    response = app.lambda_handler({"httpMethod": "GET"}, None)
    assert response["statusCode"] == 200
    assert json.loads(response["body"]) == []

Install the test dependencies and run the suite before every `sam deploy`:

pip install pytest moto boto3
python -m pytest tests/unit -v

Wiring this into a GitHub Actions workflow that runs `pytest` on every pull request, then only allows `sam deploy` to run on merges to `main`, is the difference between a personal experiment and a function a team can trust in production.

Common Pitfalls When Learning AWS Lambda

These are the mistakes that trip up almost everyone on their first serverless project, based on patterns that show up constantly in AWS’s own troubleshooting documentation and developer forums.

  • Forgetting the handler path format. The handler must be `filename.function_name`, not just the function name. If your file is `app.py` and your function is `lambda_handler`, the handler value is `app.lambda_handler` — get this wrong and every invocation fails with “Unable to import module.”
  • Deploying without running `sam build` first. SAM caches build artifacts separately from your source directory. Editing code and running `sam deploy` without `sam build` in between deploys the old, unbuilt code.
  • Setting the timeout too low. The default 3-second timeout is often too short for anything touching a database or external API on a cold start. Bump it to 10-30 seconds during development, then tune down once you understand your real latency profile.
  • Hardcoding credentials in function code. Never embed AWS keys or database passwords directly in a Lambda handler. Use environment variables for config and Secrets Manager or Parameter Store for anything sensitive.
  • Ignoring cold starts in latency-sensitive APIs. If your API needs consistent sub-100ms response times, plain Lambda without provisioned concurrency probably isn’t the right fit — the first request after idle time will always pay the cold start tax.
  • Overly broad IAM policies “to make it work.” Attaching `*` resource permissions to unblock a deploy and forgetting to scope it down afterward is one of the most common serverless security gaps found in cloud audits.
  • Not setting a Dead Letter Queue or destination for async invocations. When a Lambda triggered by S3 or SNS fails, the event can silently disappear unless you configure an on-failure destination to capture it.
  • Testing only in the console, never locally. Skipping `sam local invoke` means every debug cycle costs a full deploy, which is slow and makes iteration painful.

Expected Output at Each Stage

Here’s what success looks like at the key checkpoints, so you know whether to keep going or stop and debug.

# After Step 4 (console test)
{
  "statusCode": 200,
  "body": "{\"message\": \"Hello from Lambda!\", \"requestId\": \"a1b2c3d4-...\"}"
}
# Duration: 12.4 ms  Billed Duration: 13 ms  Memory Used: 42 MB

# After Step 8 (sam deploy output)
CloudFormation outputs from deployed stack
-------------------------------------------------------------------
Outputs
-------------------------------------------------------------------
Key                 ApiEndpoint
Description         API Gateway endpoint URL
Value                https://abc123xyz.execute-api.us-east-1.amazonaws.com/Prod/items
-------------------------------------------------------------------

# After Step 9 (S3 trigger test)
START RequestId: 5f6e7d8c-... Version: $LATEST
New object: s3://tutorial-uploads-123456789012/test.txt (13 bytes)
END RequestId: 5f6e7d8c-...
REPORT RequestId: 5f6e7d8c-... Duration: 45.20 ms Billed Duration: 46 ms

Troubleshooting Guide: Fixing the Errors You’ll Actually Hit

ErrorLikely CauseFix
“Unable to import module ‘app’: No module named ‘boto3′”Missing dependency not bundled in deployment packageAdd the package to `requirements.txt` and re-run `sam build`
Task timed out after 3.00 secondsDefault timeout too short for the operationIncrease `Timeout` in `template.yaml` under Globals or per-function
AccessDeniedException on DynamoDB callExecution role missing table permissionsAdd or verify the `DynamoDBCrudPolicy` SAM policy template targets the right table
{“message”: “Internal server error”} from API GatewayLambda returned a malformed response (missing `statusCode` or `body`)Ensure every code path returns a dict with `statusCode` and `body` keys
sam build fails with “Docker is not reachable”Docker Desktop isn’t runningStart Docker Desktop before running `sam build` or `sam local invoke`
CORS errors calling the API from a browserAPI Gateway not configured to return CORS headersAdd `Cors` config under the `Api` event source in `template.yaml`
Function works locally but fails when deployedEnvironment variable or IAM permission difference between local and cloudDiff your local `.env` against the deployed function’s environment variables in the console
Deploy hangs on “Waiting for changeset to be created”No actual changes detected, or a stuck CloudFormation stackCheck the CloudFormation console for a stack in `UPDATE_ROLLBACK_FAILED` state and resolve it before retrying
Throttling: Rate exceededConcurrent execution limit reachedRequest a service quota increase or add retry logic with exponential backoff

Advanced Tips for Production Lambda Workloads

Once the basic build works, these are the moves that separate a tutorial project from something you’d actually run in production.

  • Use Lambda Powertools for structured logging, tracing, and metrics instead of raw `print()` statements — it standardizes observability across every function in a team codebase.
  • Enable X-Ray tracing on functions that call multiple downstream services, so you can see exactly where time is spent across a Lambda-to-DynamoDB-to-S3 chain instead of guessing.
  • Use provisioned concurrency for latency-sensitive endpoints where cold starts are unacceptable, but only on the specific functions that need it — it’s billed continuously, unlike on-demand Lambda.
  • Split large functions by responsibility rather than building one giant handler with a dozen `if` branches. Smaller functions deploy faster, have smaller blast radii, and are easier to reason about in CloudWatch.
  • Version and alias your functions so you can shift traffic gradually between a new deploy and the previous stable version instead of an all-at-once cutover.
  • Set reserved concurrency on functions that could otherwise starve the rest of your account’s concurrency pool during a traffic spike.
  • Store secrets in AWS Secrets Manager and cache them in-memory across warm invocations rather than fetching them on every single call.

Lambda vs. Other AWS Compute Options

Lambda isn’t the right tool for every workload. Knowing when to reach for something else saves you from fighting the platform’s limits.

ServiceBest ForLimitation vs. Lambda
AWS LambdaShort, event-driven, bursty workloads15-minute max execution time, cold starts
AWS FargateLong-running containers, steady trafficNo true scale-to-zero, higher baseline cost for low traffic
EC2Full OS control, specialized hardware, GPUsYou manage patching, scaling, and capacity planning
AWS Step Functions + LambdaLong, multi-step workflows chaining several functionsAdds orchestration complexity and its own billing dimension

Cleaning Up to Avoid Ongoing Charges

When you’re done experimenting, tear down the stack so nothing keeps running unnecessarily. Empty the S3 bucket first, since CloudFormation won’t delete a non-empty bucket automatically.

# Empty the S3 bucket
aws s3 rm s3://tutorial-uploads-YOUR_ACCOUNT_ID --recursive

# Delete the entire stack: functions, API Gateway, DynamoDB table, IAM roles
aws cloudformation delete-stack --stack-name lambda-s3-api-tutorial

# Confirm deletion
aws cloudformation describe-stacks --stack-name lambda-s3-api-tutorial

That last command should eventually return a “stack does not exist” error, which confirms cleanup finished successfully.

Frequently Asked Questions

Is AWS Lambda free to use?
The AWS Free Tier includes a monthly allowance of requests and compute time at no cost, and this tutorial’s usage stays well within that allowance for a new or lightly used account. Beyond the Free Tier, billing is based on requests and GB-seconds of compute.

Which programming languages does Lambda support?
Lambda offers managed runtimes for Python, Node.js, Java, Go, .NET, and Ruby, plus a custom runtime option for any language via the Lambda Runtime API or container images.

What’s the maximum time a Lambda function can run?
15 minutes (900 seconds) per invocation. Workloads that regularly need longer than that should look at Step Functions for orchestration or Fargate for long-running containers.

Do I need Docker to use Lambda?
Not for deployment — Docker is only required locally if you want to use `sam local invoke` or `sam local start-api` to test functions on your machine before deploying, or if you choose to package your function as a container image instead of a ZIP archive.

What causes a Lambda cold start?
A cold start happens when AWS has to provision a brand-new execution environment for your function because no warm one is available — either it’s the first invocation ever, or enough idle time has passed that the previous environment was recycled.

Can Lambda functions talk to a VPC-based database like RDS?
Yes, by attaching the function to a VPC with the correct subnet and security group configuration. This adds a small amount of extra cold start latency due to elastic network interface setup, though AWS has significantly reduced that overhead compared to Lambda’s earlier VPC networking model.

Is SAM the only way to deploy Lambda functions?
No. You can deploy manually through the console, use raw CloudFormation, use Terraform, use the Serverless Framework, or use the AWS CDK. SAM is AWS’s own opinionated shorthand for CloudFormation aimed specifically at serverless applications, which makes it a good starting point for learning.

How much memory should I allocate to a Lambda function?
Start at 256 MB for lightweight API handlers and adjust based on CloudWatch metrics. AWS Lambda Power Tuning, an open-source tool built on Step Functions, can automatically benchmark your function across memory levels to find the cost-optimal setting.

Related Coverage

Sofia Lindström

Sofia Lindström

Editor-in-Chief

Sofia Lindström is the Editor-in-Chief at Tech Insider, where she leads editorial strategy and oversees coverage across AI, cybersecurity, and enterprise technology. With over a decade in Swedish tech journalism, she previously served as technology editor at Dagens Industri and covered the Nordic startup ecosystem for Breakit. Sofia holds an MSc in Media Technology from KTH Royal Institute of Technology and is a frequent speaker at Web Summit and Slush. She is passionate about making complex technology accessible to business leaders.

View all articles