**Quick Start:** Build a Python function, run it on your laptop with the AWS Serverless Application Model (SAM) CLI, then deploy it and delete it again. The hands-on part takes about 30 minutes. The rest of the guide covers the few ideas that decide whether a Lambda function behaves in production. A Lambda function looks like the easiest code you will ever ship: one function, no servers, a bill measured in milliseconds. Then a queue delivers the same message twice, a timeout you never set kills a request after 3 seconds, and the first call after lunch takes a full second longer than the rest. Every one of those surprises comes from the same short list of concepts, and this guide covers that list. ## What You'll Learn * What is AWS Lambda, and what changed in 2026? * What are the use cases for Lambda, and when is it the wrong tool? * What are the core concepts you need to be productive: handlers, the execution environment, invocation types, concurrency, packaging, observability, and cost? * How do you build, test, deploy, and delete a function with AWS SAM? * Where can you go to learn more about Lambda? ## The Basics AWS Lambda runs your code when something happens, such as an HTTP request, a file landing in Amazon S3, or a message arriving on a queue. You pay per request plus the time that code runs. You hand AWS a function; AWS supplies the servers, the operating system, the scaling, and the patching. AWS now calls the classic Lambda function this guide teaches an *Event Function*, although the API operations, SDKs, CLI, and the `AWS::Lambda::Function` resource type still say "function" ([Lambda Event Functions][dg-event-functions]). Every concept below applies to Event Functions. Two newer options sit beside it. Lambda Web Functions run ordinary web servers, and Lambda MicroVMs provide isolated sandboxes for AI agents and build jobs. The end of this guide covers both, along with durable functions. This guide teaches the 20% of Lambda that covers 80% of real work. That 20% is the handler, the execution environment and cold starts, memory sizing, the three invocation types, concurrency, packaging, logging, and the cost model. The long tail, including virtual private cloud (VPC) networking, extensions, custom runtimes, and Lambda@Edge, lives in the resources at the end. ### Primary Use Cases * APIs and webhooks behind Amazon API Gateway or a function URL. * File processing when objects land in S3, such as resizing images or parsing uploads. * Queue and stream consumers for Amazon Simple Queue Service (SQS), Amazon Kinesis, and DynamoDB Streams. * Scheduled jobs and glue code between AWS services. ### Less Suitable Use Cases * Work that runs longer than 15 minutes in one go, since that is the timeout limit on default compute ([Lambda quotas][dg-limits]). * Steady, high-volume traffic, where an always-on container or instance can cost less per request. * Long-running stateful workflows written as one function. Use AWS Step Functions or Lambda durable functions instead ([durable functions][dg-durable]). ### When to Use AWS Lambda Reach for Lambda when work arrives in bursts, each piece finishes in seconds or minutes, and you would rather not run servers. Skip it when traffic is flat and heavy, when a job needs more than 15 minutes, or when the code needs a long-lived process holding state in memory. ## The 20% That Does 80% ### The handler, the event, and the context Lambda calls one function in your code, the handler, and passes it two things ([Python handler][dg-python-handler]). The event is a JSON document whose shape depends on what invoked the function. The context object carries details such as the request ID and the time left before the timeout. ```python def handler(event, context): name = event.get("name", "world") return {"message": f"Hello, {name}!"} ``` **What this gives you:** A function Lambda can call with any JSON event. The return value goes back to the caller on a synchronous invocation. ### The execution environment and cold starts Lambda runs your function inside an execution environment, a small isolated machine with your runtime and code loaded. Each environment moves through three phases: Init, Invoke, and Shutdown ([execution environment lifecycle][dg-lifecycle]). ```mermaid graph TB A[Init: start the runtime and run code outside the handler] --> B[Invoke: run the handler for one event] B --> C{Another event arrives soon?} C -->|Yes: warm start| B C -->|No| D[Shutdown] ``` A *cold start* is an invocation that has to wait for Init. AWS says cold starts typically occur in under 1% of invocations and take from under 100 milliseconds to over a second, and you pay for Init time ([execution environment lifecycle][dg-lifecycle]). On-demand Init is limited to 10 seconds; provisioned concurrency and SnapStart allow up to 15 minutes. The practical rule follows from the diagram. Code at the top of your file runs once per environment and gets reused by every warm invocation, so put SDK clients, database connections, and configuration there ([Lambda best practices][dg-best-practices]). George Mao refines the rule in his [Lambda development guide][mao-guide]: lazy-load anything only some code paths need. A secret used by one route, for example, should not slow every cold start. A Reddit thread asking [what a well-made Python Lambda looks like][reddit-wellmade] lands in the same place, and adds a warning: `/tmp` survives between warm invocations too, so give each request its own directory. ### Memory is the CPU dial You set memory from 128 MB to 10,240 MB, and Lambda allocates CPU in proportion; at 1,769 MB a function gets the equivalent of one virtual CPU ([memory configuration][dg-memory]). A CPU-bound function that is slow at 128 MB may finish fast enough at 1,024 MB to cost less overall. Measure instead of guessing. The AWS best practices page recommends the open source [AWS Lambda Power Tuning][power-tuning] tool. It runs your function at several memory settings and reports the cheapest, the fastest, or a balance of the two. Luca Mezzalira and Matt Diamond [reported cutting a function's cold start by 56% and its execution time by 95%][mezzalira-post] in the Serverless Office Hours episode on [Lambda performance tuning][office-hours-tuning]. ### Three ways Lambda gets invoked How an event reaches your function decides who retries a failure ([retries][dg-retries]): * **Synchronous.** The caller waits for the response, as API Gateway and a default `Invoke` call (`RequestResponse`) do. Lambda does not retry; the caller decides. * **Asynchronous.** Services such as S3 and Amazon Simple Notification Service (SNS) hand Lambda the event and move on. Lambda retries function errors twice ([retries][dg-retries]) and can send the final failure to a destination or a dead-letter queue ([asynchronous invocation][dg-async]). * **Event source mapping.** Lambda polls SQS, Kinesis, DynamoDB Streams, and Kafka for you and calls your function with batches. Records arrive at least once ([event source mappings][dg-esm]). Asynchronous retries and at-least-once delivery mean the same event can run twice. Write the handler so a second run does no harm: check a processed-ID table before charging a card, for example. AWS says to "Write idempotent code" ([Lambda best practices][dg-best-practices]). For SQS batches, turn on `ReportBatchItemFailures` and return only the failed message IDs, so one bad message does not send the whole batch back to the queue ([SQS error handling][dg-sqs-errors]). ### Concurrency: reserved, provisioned, and SnapStart On Lambda's default compute, each execution environment handles one request at a time, so 50 simultaneous requests need 50 environments. The default quota is 1,000 concurrent executions per Region, though new accounts start lower and AWS raises the quota automatically as usage grows ([Lambda quotas][dg-limits]). Three settings shape that: * **Reserved concurrency** caps one function and guarantees it a slice of the account limit. Setting it to 0 stops a runaway function ([reserved concurrency][dg-concurrency]). * **Provisioned concurrency** keeps environments initialized ahead of traffic to remove cold starts. You pay for it while it is configured, whether or not requests arrive ([provisioned concurrency][dg-provisioned]). * **SnapStart** snapshots an initialized environment and restores it on a cold start. It supports Java 11 and later, Python 3.12 and later, and .NET 8 and later, but not Node.js. Java SnapStart costs nothing extra; Python and .NET add charges for caching and restoring snapshots ([SnapStart][dg-snapstart]). ### Packaging: .zip, container image, and layers Ship code as a .zip archive (250 MB unzipped, including layers) or a container image (up to 10 GB) ([Lambda quotas][dg-limits]). Start with .zip; switch to an image when dependencies outgrow the limit or your team already builds containers. You cannot change an existing function's package type later ([container images][dg-images]). Layers share dependencies across functions, but the Reddit thread's advice holds: treat them as a last resort, and package shared code as a normal library first. Keep test and build tools out of the deployment package, and use current SDKs and runtimes ([Mao][mao-guide]). Mao's 2024 list of "latest runtimes" has already aged, so check the [supported runtimes page][dg-runtimes] before you pick one. ### Observability: logs, the REPORT line, and Powertools Everything your function prints goes to Amazon CloudWatch Logs. Every invocation ends with a `REPORT` line showing duration, billed duration, memory size, and `Max Memory Used`, the number you size memory from ([Lambda best practices][dg-best-practices]). [Powertools for AWS Lambda][powertools] adds structured JSON logging, tracing, custom metrics, idempotency, and batch processing for Python, TypeScript, Java, and .NET. Darryl Ruggles calls it "best practices by default" in his [Powertools write-up][ruggles-powertools], and warns that logging the full event can leak personal data in production. For tracing, plan on OpenTelemetry: the AWS X-Ray SDKs and daemon entered maintenance mode on February 25, 2026, and now get security fixes only ([X-Ray SDK and daemon support timeline][xray-eos]). ### The limits worth memorizing **Time** 15 minutes maximum per invocation. The default timeout is 3 seconds, so set it on purpose ([timeout][dg-timeout]). **Memory and disk** 128 MB to 10,240 MB of memory, and 512 MB to 10,240 MB of `/tmp` ([ephemeral storage][dg-tmp]). **Payload** 6 MB for a synchronous request or response, 1 MB for an asynchronous event ([Lambda quotas][dg-limits]). **Concurrency** 1,000 concurrent executions per Region by default (new accounts start lower), raisable by request ([Lambda quotas][dg-limits]). ### How Lambda bills you You pay per request and per GB-second of duration, billed by the millisecond. The free tier includes one million requests and 400,000 GB-seconds per month, and beyond it, requests cost $0.20 per million ([Lambda pricing][pricing]). Init time counts as duration. Arm functions on AWS Graviton cost less per GB-second than x86; AWS launched them in 2021 claiming up to 34% better price performance ([Graviton2 launch][graviton]). ## Build Something: A Function You Run Locally, Deploy, and Delete ### Prerequisites **Required:** * An AWS account and the AWS CLI configured with credentials (`aws configure`). * Permission in that account to create CloudFormation stacks, AWS Identity and Access Management (IAM) roles, S3 buckets, and Lambda functions. An administrator user in a personal account has all four. * The [AWS SAM CLI][sam-docs]. This guide used version 1.166.2. * Docker, running, for local invocation. * About 30 minutes. **Not required:** Python on your machine. This function has no dependencies, so `sam build` only copies the code, and `sam local invoke` runs it in a container that matches the Lambda runtime. If you add a `requirements.txt` later, run `sam build --use-container` unless Python 3.14 is installed locally. ### Step 1: Write the function Create a project folder with a `src` folder inside it: ```bash mkdir -p learn-lambda/src cd learn-lambda ``` Save this as `src/app.py`: ```python import json import logging import os logger = logging.getLogger() logger.setLevel(os.environ.get("LOG_LEVEL", "INFO")) # Code outside the handler runs once per execution environment, then gets reused. GREETING = os.environ.get("GREETING", "Hello") def handler(event, context): name = event.get("name", "world") logger.info(json.dumps({"event": "greeted", "name": name})) return { "message": f"{GREETING}, {name}!", "remaining_ms": context.get_remaining_time_in_millis(), } ``` **What you learned:** Configuration loads once at the top; the handler does only per-request work and logs one JSON line per call. ### Step 2: Describe the function in a SAM template Save this as `template.yaml` in the project folder: ```yaml AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: A first Lambda function Resources: GreeterFunction: Type: AWS::Serverless::Function Properties: FunctionName: greeter CodeUri: src/ Handler: app.handler Runtime: python3.14 Architectures: - arm64 MemorySize: 256 Timeout: 10 Environment: Variables: GREETING: Hello ``` `Handler: app.handler` means "the `handler` function in `app.py`". The template sets memory and timeout explicitly instead of accepting the 3-second default, and picks Arm. ### Step 3: Build and invoke locally Save a test event as `event.json`: ```json {"name": "Ada"} ``` Then build and invoke: ```bash sam build sam local invoke GreeterFunction --event event.json ``` **Expected output** (the last lines; the request ID and `remaining_ms` vary): ```text [INFO] 2026-10-14T02:44:43.202Z ee087417-... {"event": "greeted", "name": "Ada"} END RequestId: ee087417-... REPORT RequestId: ee087417-... Duration: 153.34 ms Billed Duration: 154 ms Memory Size: 256 MB ... {"message": "Hello, Ada!", "remaining_ms": 9848} ``` **What you learned:** The `REPORT` line you will read in CloudWatch shows up locally too. `sam local invoke` runs the function once in a Docker container ([sam local invoke][sam-local-invoke]). ### Step 4 (optional): Test a container image the same way If you package as a container image, AWS base images include a runtime interface emulator for local testing ([Python container images][dg-python-image]). Save this as `Dockerfile` in the project folder: ```dockerfile FROM public.ecr.aws/lambda/python:3.14 COPY src/app.py ${LAMBDA_TASK_ROOT} CMD ["app.handler"] ``` Build it, run it, and send it an event: ```bash docker build -t greeter:test . docker run -d --name greeter-test -p 9000:8080 greeter:test curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"name": "Ada"}' docker rm -f greeter-test ``` **Expected output** from `curl`: ```json {"message": "Hello, Ada!", "remaining_ms": 299960} ``` The emulator ignores your template's timeout and uses its own default, which is why `remaining_ms` is larger here. ### Step 5: Deploy ```bash sam deploy --guided ``` Name the stack `learn-lambda`, pick a Region, and accept the defaults for the rest. SAM saves your answers to `samconfig.toml`, so later deploys need only `sam deploy`. **Expected output:** the last lines report `Successfully created/updated stack - learn-lambda` and the Region you picked. The stack creates an IAM role and the function, and Step 7 removes both. The first deploy in a Region also creates a separate `aws-sam-cli-managed-default` stack for SAM's artifact bucket; Step 7 explains why it stays. ### Step 6: Invoke it in the cloud and read the logs ```bash sam remote invoke GreeterFunction --stack-name learn-lambda --event '{"name": "Ada"}' sam logs -n GreeterFunction --stack-name learn-lambda --tail ``` **Expected output** from `sam remote invoke` includes the same `message` as the local run; `remaining_ms` differs: ```text {"message": "Hello, Ada!", "remaining_ms": } ``` Stop the log tail with Ctrl-C once you have seen the `REPORT` line, and note `Max Memory Used` against the 256 MB you set. ### Step 7: Delete it ```bash sam delete --stack-name learn-lambda ``` Answer `y` to both prompts, one to delete the stack and one to delete the artifacts SAM uploaded for it. Both default to No, so pressing Enter keeps everything. To skip the prompts, add `--no-prompts`. **Expected output:** the last line reads `Deleted successfully`. Two things stay: * The `aws-sam-cli-managed-default` stack. It holds the S3 bucket SAM uses for deployment artifacts and reuses for later deploys in that Region. * The `/aws/lambda/greeter` log group in CloudWatch Logs. Lambda created it when the function first ran, so it is not part of the stack ([Lambda logs in CloudWatch][lambda-cloudwatch-logs]). Delete it with `aws logs delete-log-group --log-group-name /aws/lambda/greeter` if you want it gone. Delete it before you follow the CDK section, whose stack creates a log group with that name. **What you learned:** The whole loop, from a file on your laptop to a running function and back to nothing, takes six SAM commands. ## Deploy the Same Function with the AWS CDK The AWS Cloud Development Kit (CDK) describes infrastructure in a general-purpose language and deploys it through CloudFormation. The stack below deploys the function from Step 1 in TypeScript, with the same name, runtime, handler, architecture, memory, timeout, and `GREETING` as the SAM template, so any difference you notice comes from the tool, not the function. Every command in this section runs on your own machine. **Verified with:** Node.js 26.9.0, AWS CDK CLI 2.1145.0, and aws-cdk-lib 2.273.0. With these versions, `cdk init` created the project below and `cdk synth` turned the stack into a CloudFormation template with one Lambda function. **Required:** * Node.js and the AWS CDK CLI, installed with `npm install -g aws-cdk` ([Getting started with the AWS CDK][cdk-getting-started]). * The same AWS credentials and permissions as the SAM steps. * The SAM stack deleted (Step 7). Both deploys name the function `greeter`, and Lambda rejects a new function whose name already exists in the account and Region ([CreateFunction][lambda-create-function]). * No `/aws/lambda/greeter` log group (Step 7 shows how to delete it). The stack creates a log group with that name, and the deploy fails if one already exists ([CDK feature flag `useCdkManagedLogGroup`][cdk-managed-log-group]). ### Create the CDK project From the `learn-lambda` folder, create a project folder next to `src` and generate a TypeScript app in it: ```bash {practice="machine"} mkdir greeter-cdk cd greeter-cdk cdk init app --language typescript ``` ### Define the function Replace the contents of `lib/greeter-cdk-stack.ts` with: ```typescript {practice="machine"} import * as path from 'path'; import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as lambda from 'aws-cdk-lib/aws-lambda'; export class GreeterCdkStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); new lambda.Function(this, 'GreeterFunction', { functionName: 'greeter', code: lambda.Code.fromAsset(path.join(__dirname, '../../src')), handler: 'app.handler', runtime: lambda.Runtime.PYTHON_3_14, architecture: lambda.Architecture.ARM_64, memorySize: 256, timeout: cdk.Duration.seconds(10), environment: { GREETING: 'Hello', }, }); } } ``` `Code.fromAsset` points at the same `src` folder that `CodeUri` names in the SAM template. The stack sets no `role`, so the CDK generates an execution role for the function ([Function construct][cdk-lambda-function]). ### Bootstrap once per account and Region The CDK deploys into an environment, one account and one Region. Run `cdk bootstrap` once from the project folder before you deploy into an environment for the first time. It deploys a CloudFormation stack named `CDKToolkit` with an S3 bucket for assets such as your function code, an Amazon ECR repository, and IAM roles the CDK uses to deploy ([AWS CDK bootstrapping][cdk-bootstrapping]). Running it again on an environment that is already bootstrapped upgrades the stack if needed and otherwise does nothing ([Bootstrap your environment][cdk-bootstrap-env]). ```bash {practice="machine"} cdk bootstrap ``` ### Deploy ```bash {practice="machine"} cdk deploy ``` Because the stack creates an IAM role, `cdk deploy` lists the IAM changes and asks you to confirm before it deploys. Answer `y` ([cdk deploy][cdk-deploy]). `cdk deploy` reports its progress as it builds, publishes, and deploys, then prints the new stack's ARN after `Stack ARN:`. In a run of this stack in `us-east-1`, the ARN looked like this, with your account ID in place of `123456789012`: ```text arn:aws:cloudformation:us-east-1:123456789012:stack/GreeterCdkStack/bca1c440-c890-11f1-a12f-0e10bad081d1 ``` That run skipped the confirmation with `--require-approval never`; your run stops to ask first. ### Invoke it ```bash {practice="machine"} aws lambda invoke --function-name greeter --cli-binary-format raw-in-base64-out --payload '{"name": "Ada"}' response.json cat response.json ``` `--cli-binary-format raw-in-base64-out` lets AWS CLI version 2 accept the payload as plain JSON. `aws lambda invoke` prints the status of the call: ```text { "StatusCode": 200, "ExecutedVersion": "$LATEST" } ``` `cat response.json` then prints what the function returned, the same `message` as the SAM run: ```text {"message": "Hello, Ada!", "remaining_ms": 9999} ``` ### Troubleshooting the CDK deploy If `cdk bootstrap` or `cdk deploy` fails because it finds no AWS credentials, apply the fix from the `Error: Unable to locate credentials` entry in [Troubleshooting][troubleshooting], then run the command again. ### Clean up ```bash {practice="machine"} cdk destroy ``` `cdk destroy` asks you to confirm, then deletes the stack and the function and role it created ([cdk destroy][cdk-destroy]). The `CDKToolkit` bootstrap stack stays, with its S3 bucket, ECR repository, and IAM roles, because it is a separate stack that later deploys in the same account and Region reuse ([AWS CDK bootstrapping][cdk-bootstrapping]). The CDK documentation advises against deleting and recreating a bootstrap stack; run `cdk bootstrap` again to upgrade it instead ([Bootstrap your environment][cdk-bootstrap-env]). The `/aws/lambda/greeter` log group in CloudWatch Logs also stays. A project made with `cdk init` turns on the `useCdkManagedLogGroup` feature flag in `cdk.json`, so the stack creates the log group itself instead of leaving that to Lambda ([Lambda log groups in the CDK][cdk-lambda-readme]), and marks it with a `Retain` deletion policy, which keeps a resource when its stack is deleted ([DeletionPolicy attribute][cfn-deletion-policy]). Delete it with `aws logs delete-log-group --log-group-name /aws/lambda/greeter` if you want it gone, and before you run `cdk deploy` again. [cdk-getting-started]: https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html [cdk-lambda-function]: https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.Function.html [cdk-bootstrapping]: https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html [cdk-bootstrap-env]: https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping-env.html [cdk-deploy]: https://docs.aws.amazon.com/cdk/v2/guide/ref-cli-cmd-deploy.html [cdk-destroy]: https://docs.aws.amazon.com/cdk/v2/guide/ref-cli-cmd-destroy.html [lambda-create-function]: https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html [cdk-managed-log-group]: https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md#aws-cdkaws-lambdausecdkmanagedloggroup [cdk-lambda-readme]: https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda-readme.html [cfn-deletion-policy]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html [troubleshooting]: #troubleshooting ## Deploy the Same Function with Terraform Terraform describes infrastructure in HashiCorp Configuration Language (HCL) and keeps track of what it created in its own state file instead of a CloudFormation stack. The configuration below deploys the function from Step 1 with the same name, runtime, handler, architecture, memory, timeout, and `GREETING` as the SAM template and the CDK stack. SAM and the CDK generated the execution role for you; here the configuration declares the role and attaches the basic execution policy that lets the function write its logs. Every command in this section runs on your own machine. **Verified with:** Terraform 1.16.3, hashicorp/aws 6.69.0, and hashicorp/archive 2.8.1. With these versions, `terraform init`, `terraform validate`, and `terraform fmt -check` all succeeded on the configuration below. **Required:** * The Terraform CLI ([Install Terraform][tf-install]). * The same AWS credentials and permissions as the SAM steps. * No other deployed `greeter` function. If you followed the CDK section, run `cdk destroy` from its clean-up step first; otherwise delete the SAM stack (Step 7). All three deploys name the function `greeter`, and Lambda rejects a new function whose name already exists in the account and Region ([CreateFunction][lambda-create-function]). ### Write the configuration From the `learn-lambda` folder, create a project folder next to `src`: ```bash {practice="machine"} mkdir greeter-terraform cd greeter-terraform ``` Save this as `main.tf`: ```hcl {practice="machine"} terraform { required_providers { aws = { source = "hashicorp/aws" version = "6.69.0" } archive = { source = "hashicorp/archive" version = "2.8.1" } } } provider "aws" { region = "us-east-1" } data "archive_file" "greeter" { type = "zip" source_dir = "${path.module}/../src" output_path = "${path.module}/greeter.zip" } resource "aws_iam_role" "greeter" { name = "learn-lambda-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } }, ] }) } resource "aws_iam_role_policy_attachment" "greeter_logs" { role = aws_iam_role.greeter.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } resource "aws_lambda_function" "greeter" { function_name = "greeter" filename = data.archive_file.greeter.output_path code_sha256 = data.archive_file.greeter.output_base64sha256 role = aws_iam_role.greeter.arn handler = "app.handler" runtime = "python3.14" architectures = ["arm64"] memory_size = 256 timeout = 10 environment { variables = { GREETING = "Hello" } } } ``` The `archive_file` data source zips the same `src` folder that `CodeUri` names in the SAM template ([archive_file][tf-archive-file]), and `code_sha256` makes Terraform update the function when that zip changes ([aws_lambda_function][tf-lambda-function]). The role trusts the Lambda service, and the `AWSLambdaBasicExecutionRole` managed policy grants the CloudWatch Logs permissions the function needs ([Lambda execution role][lambda-execution-role]). Each `version` pins the exact provider release this guide was verified with. The AWS provider accepts the `python3.14` runtime from 6.21.0 ([AWS provider changelog][tf-aws-changelog]); to move to a newer release, change the `version` and run `terraform init -upgrade` ([terraform init][tf-init]). The `region` in the `provider` block is the only place the configuration names a Region ([AWS provider configuration][tf-aws-provider]). To deploy somewhere else, change `us-east-1` there and nowhere else. ### Deploy ```bash {practice="machine"} terraform init terraform apply ``` `terraform init` installs the providers listed in `required_providers` ([terraform init][tf-init]). `terraform apply` shows its plan and asks you to approve it before it creates the role, attaches the policy, and creates the function ([terraform apply][tf-apply]). Type `yes` to approve. When the apply finishes, the last line reports the result: ```text Apply complete! Resources: 3 added, 0 changed, 0 destroyed. ``` These lines come from a run that approved the plan with `-auto-approve`, so it never stopped to ask. Terraform records what it created in a `terraform.tfstate` file in the project folder, because the default `local` backend stores state on your disk ([Backend block][tf-backend]). Keep that file until you clean up. ### Invoke it ```bash {practice="machine"} aws lambda invoke --function-name greeter --cli-binary-format raw-in-base64-out --payload '{"name": "Ada"}' response.json cat response.json ``` `response.json` holds the same `message` as the SAM run: ```text {"message": "Hello, Ada!", "remaining_ms": 9999} ``` If your AWS CLI's default Region differs from the one in `main.tf`, add `--region` with the Region from `main.tf`. ### Troubleshooting the Terraform deploy If `terraform apply` fails because it finds no valid AWS credentials, apply the fix from the `Error: Unable to locate credentials` entry in [Troubleshooting][troubleshooting], then run the command again. The AWS provider looks for credentials in the same order as the AWS CLI, so the CLI fix applies here too ([AWS provider authentication][tf-aws-provider]). ### Clean up ```bash {practice="machine"} terraform destroy ``` `terraform destroy` shows what it will delete and asks you to approve, then removes everything the configuration created: the function, the policy attachment, and the role ([terraform destroy][tf-destroy]). After you type `yes`, it ends with: ```text Destroy complete! Resources: 3 destroyed. ``` No artifact bucket or bootstrap stack stays behind, because Terraform kept its records in the local state file. Two things do stay: * The `/aws/lambda/greeter` log group in CloudWatch Logs. Lambda created it when the function first ran, unless an earlier deploy left one with that name, so it is not part of the configuration ([Lambda logs in CloudWatch][lambda-cloudwatch-logs]). Delete it with `aws logs delete-log-group --log-group-name /aws/lambda/greeter` if you want it gone. * The `greeter.zip` file and the state file in the project folder. Delete the folder when you are done. [tf-install]: https://developer.hashicorp.com/terraform/install [tf-archive-file]: https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file [tf-lambda-function]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function [lambda-execution-role]: https://docs.aws.amazon.com/lambda/latest/dg/permissions-executionrole.html [tf-aws-changelog]: https://github.com/hashicorp/terraform-provider-aws/blob/main/CHANGELOG.md [tf-aws-provider]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs [tf-init]: https://developer.hashicorp.com/terraform/cli/commands/init [tf-apply]: https://developer.hashicorp.com/terraform/cli/commands/apply [tf-backend]: https://developer.hashicorp.com/terraform/language/backend [tf-destroy]: https://developer.hashicorp.com/terraform/cli/commands/destroy [lambda-cloudwatch-logs]: https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html ## Manage the Function with the AWS CLI SAM, the CDK, and Terraform each wrap the same few Lambda API calls. The AWS CLI makes those calls one at a time, so you see what each one sends and what AWS answers. This section creates the same `greeter` function by hand, invokes it, and reads the answers. Every output below is what AWS CLI 2.38.0 printed against a real account, with the account ID replaced by `123456789012`. Before you start on your own account: * The AWS CLI v2 installed and signed in to an account ([install the AWS CLI][cli-install]). * No deployed `greeter` function. Run the clean-up step of any deploy section you followed first, because Lambda rejects a second function with the same name in the same Region. ### Check which account the CLI uses ```zsh {practice="machine"} aws sts get-caller-identity ``` The answer names your account ID, your user or role, and its ARN, so no output appears here. Read it before you create anything: the next commands create a role and a function in that account. ### Create the execution role Every function runs as an IAM role, its execution role ([Lambda execution role][lambda-execution-role]). On your own machine, make a folder for the function and copy `src/app.py` from Step 1 into it. Then package the code, save a test event, and write the trust policy that lets Lambda assume the role: ```zsh {practice="machine"} mkdir -p ~/greeter && cd ~/greeter zip function.zip app.py echo '{"name": "Ada"}' > event.json echo '{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' > trust.json ``` Then create the role, attach the managed policy that lets the function write its logs, and wait until IAM reports the role: ```zsh {practice="machine"} aws iam create-role --role-name learn-lambda-role --assume-role-policy-document file://trust.json aws iam attach-role-policy --role-name learn-lambda-role --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole aws iam wait role-exists --role-name learn-lambda-role ``` Save the role's ARN in `ROLE_ARN`, so the `create-function` examples work in your shell as printed: ```zsh {practice="machine"} ROLE_ARN=$(aws iam get-role --role-name learn-lambda-role --query Role.Arn --output text) ``` A new role can take a few seconds to reach Lambda. If `create-function` answers that the role cannot be assumed, wait ten seconds and run it again. ### Start from an empty Region ```zsh aws --version aws lambda list-functions ``` ```text aws-cli/2.38.0 Python/3.14.6 Linux/7.0.12-linuxkit docker/x86_64.amzn.2023 { "Functions": [] } ``` `aws --version` names the CLI release, then the Python and platform it runs on. An empty `Functions` list means the Region holds no function yet. ### Create and invoke the function `create-function` uploads the zip and sets the runtime, handler, architecture, memory, timeout, and environment in one call ([create-function][cli-create-function]): ```zsh cd ~/greeter aws lambda create-function --function-name greeter --runtime python3.14 --role "$ROLE_ARN" --handler app.handler --architectures arm64 --memory-size 256 --timeout 10 --environment 'Variables={GREETING=Hello}' --zip-file fileb://function.zip aws lambda get-function --function-name greeter --query Configuration.State --output text aws lambda invoke --function-name greeter --payload fileb://event.json response.json cat response.json; echo ``` ```text { "FunctionName": "greeter", "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:greeter", "Runtime": "python3.14", "Role": "arn:aws:iam::123456789012:role/learn-lambda-role", "Handler": "app.handler", "CodeSize": 619, "Description": "", "Timeout": 10, "MemorySize": 256, "LastModified": "2026-10-17T01:43:01.782+0000", "CodeSha256": "cgTW8vA3pzrPZ7VcRsVSuiaXKPmbY1IqoAr5MBWjHG8=", "Version": "$LATEST", "Environment": { "Variables": { "GREETING": "Hello" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "7c2e1e5b-14b1-4451-908d-59a1fd3595b0", "State": "Pending", "StateReason": "The function is being created.", "StateReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "arm64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:ab559f26fdef82cfdfbb2942d07b46f84b45b52d0d760d3d623001ab0218052e" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/greeter" } } Active { "StatusCode": 200, "ExecutedVersion": "$LATEST" } {"message": "Hello, Ada!", "remaining_ms": 9999} ``` The `create-function` answer says `"State": "Pending"`: Lambda is still preparing the function. A moment later `get-function` reports `Active`, and `invoke` works. `invoke` prints the call's status, and the function's own response lands in `response.json` ([invoke][cli-invoke]). `cat response.json; echo` prints that file and then a newline, because the response ends without one. Your own zip gives a different `CodeSize` and `CodeSha256` unless it is byte for byte the recorded one. On your own account, wait for `Active` before you invoke, instead of guessing how long it takes ([function states][lambda-states]): ```zsh {practice="machine"} aws lambda wait function-active-v2 --function-name greeter ``` The Command Reference below holds every recorded command, including updates, deletes, and the errors you meet when the function does not exist. When you are done on your own account, delete the function, the role, and the log group: ```zsh {practice="machine"} aws lambda delete-function --function-name greeter aws iam detach-role-policy --role-name learn-lambda-role --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole aws iam delete-role --role-name learn-lambda-role aws logs delete-log-group --log-group-name /aws/lambda/greeter ``` [cli-install]: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html [cli-create-function]: https://docs.aws.amazon.com/cli/latest/reference/lambda/create-function.html [cli-invoke]: https://docs.aws.amazon.com/cli/latest/reference/lambda/invoke.html [lambda-states]: https://docs.aws.amazon.com/lambda/latest/dg/functions-states.html ## Practice the AWS CLI The practice shell below runs real zsh 5.9.2 in this page, with a simulated AWS CLI that answers from recordings of a real account. It answers only these recorded aws commands: `lambda create-function`, `invoke`, `get-function`, `list-functions`, `update-function-code`, `update-function-configuration`, `delete-function`, and `aws --version`. Every answer is what AWS CLI 2.38.0 printed in us-east-1, so you can practice the Lambda commands without an account, a bill, or a function left behind. Nothing downloads until you press Start, nothing you type leaves your browser, and Reset shell brings back a fresh shell with an empty Region. The practice shell needs JavaScript; every command in the Command Reference also works with the AWS CLI on your own machine and account. The shell's home holds the `greeter` folder from [Create the execution role][create-role], with `function.zip`, `function-v2.zip`, and `event.json`, and `ROLE_ARN` is already set. The practice shell differs from the AWS CLI on your own machine in the ways below. Run anything that cannot run here on your own machine and account: * `aws` is a simulation that answers only from recordings * Only the listed commands and recorded forms answer. Forms the real CLI accepts but the shell does not model get a note: abbreviated and repeated flags, optional-value flags, a list flag (`--architectures`, `--file-system-configs`) given no value or more than one, values starting with `-` other than the negative numbers `-1`, `-0.5`, and `-.5` (so `-5.`, `-1e3`, and non-ASCII digits get a note, though the real CLI takes them as values), `--`, an `invoke` outfile that is `-`, starts with `-`, is `.` or `..`, or contains `/`, a missing `~` file, any `AWS_*` environment variable, and any unrecorded value * No credentials are read or needed, so `aws configure` and the other credential commands explain that none are used here * Values that vary on real AWS are frozen at the recorded values: `LastModified`, `RevisionId`, `RuntimeVersionArn`, and `remaining_ms` * Asynchronous transitions complete at once: a new function shows `State: Pending` and an update shows `LastUpdateStatus: InProgress`, as recorded, and the next command already sees the finished state * `get-function`'s presigned `Code.Location` is the placeholder `https://presigned-url.invalid/removed` * `aws --version` prints the line recorded in the pinned Linux container, `aws-cli/2.38.0 Python/3.14.6 Linux/7.0.12-linuxkit docker/x86_64.amzn.2023`, which names the recording machine's Docker VM kernel and the image's architecture; your machine prints its own OS and install type * The practice home presets `ROLE_ARN` and `~/.aws/config` * No network: the kernel has no IPv4 (`INET`), so `wget`, `ping`, `nc`, and `nslookup` fail * The practice config turns off the CLI's pager (`cli_pager =`); your machine pages long output with `less` unless you set the same line * A note exits with status 3, a status outside the CLI's documented set (0, 1, 2, 130, 252 to 255) * Forms the real CLI treats as the same request share one answer: `--flag=value` and `--flag value`, any option or global order, the same file bytes under another name or a `~` path, another bare outfile name, `--version` added to a listed command, and `--output json` or `--no-cli-pager` spelled out * Recorded usage errors replay only on an 80-column terminal with the recorded color settings. With stdout piped or redirected, a terminal of another width, or other color settings (`NO_COLOR`, `FORCE_COLOR`, or `PYTHON_COLORS` set, or another `TERM`), the shell prints the base note instead; the real CLI prints the usage uncolored, or wrapped to your width `sam`, `cdk`, `terraform`, `python3`, `pip`, `docker`, `curl`, and `zip` are not installed, so zsh answers `command not found`. Run them on your own machine too. Run the drill below in the practice shell, in order, from a fresh shell. Type each command exactly as shown and press Enter. Each step shows what the shell prints, which is what real AWS answered. It takes about ten minutes: 1. Check the version: `aws --version`. The shell names the recorded CLI release, then the Python and the Linux container it ran in. Your own machine prints its own platform. ```text aws-cli/2.38.0 Python/3.14.6 Linux/7.0.12-linuxkit docker/x86_64.amzn.2023 ``` 2. List the functions in the Region: `aws lambda list-functions`. The list is empty: nothing exists yet. ```text { "Functions": [] } ``` 3. Move into the function's folder: `cd greeter`. It prints nothing, and the prompt changes to `reader@practice ~/greeter %`. The folder holds `function.zip`, `function-v2.zip`, and `event.json`. 4. Create the function: `aws lambda create-function --function-name greeter --runtime python3.14 --role "$ROLE_ARN" --handler app.handler --architectures arm64 --memory-size 256 --timeout 10 --environment 'Variables={GREETING=Hello}' --zip-file fileb://function.zip`. `ROLE_ARN` is already set in the practice shell. The answer says `"State": "Pending"`, because Lambda is still preparing the function. ```text { "FunctionName": "greeter", "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:greeter", "Runtime": "python3.14", "Role": "arn:aws:iam::123456789012:role/learn-lambda-role", "Handler": "app.handler", "CodeSize": 619, "Description": "", "Timeout": 10, "MemorySize": 256, "LastModified": "2026-10-17T01:43:01.782+0000", "CodeSha256": "cgTW8vA3pzrPZ7VcRsVSuiaXKPmbY1IqoAr5MBWjHG8=", "Version": "$LATEST", "Environment": { "Variables": { "GREETING": "Hello" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "7c2e1e5b-14b1-4451-908d-59a1fd3595b0", "State": "Pending", "StateReason": "The function is being created.", "StateReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "arm64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:ab559f26fdef82cfdfbb2942d07b46f84b45b52d0d760d3d623001ab0218052e" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/greeter" } } ``` 5. Invoke it with the test event: `aws lambda invoke --function-name greeter --payload fileb://event.json response.json`. `invoke` prints the call's status. The function's own response lands in `response.json`. ```text { "StatusCode": 200, "ExecutedVersion": "$LATEST" } ``` 6. Read the function's response: `cat response.json; echo`. `echo` adds the newline the response lacks. ```text {"message": "Hello, Ada!", "remaining_ms": 9999} ``` 7. Change the greeting: `aws lambda update-function-configuration --function-name greeter --environment 'Variables={GREETING=Hi}'`. The answer shows `"GREETING": "Hi"` and `"LastUpdateStatus": "InProgress"`: the update is still rolling out. ```text { "FunctionName": "greeter", "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:greeter", "Runtime": "python3.14", "Role": "arn:aws:iam::123456789012:role/learn-lambda-role", "Handler": "app.handler", "CodeSize": 619, "Description": "", "Timeout": 10, "MemorySize": 256, "LastModified": "2026-10-17T01:43:35.000+0000", "CodeSha256": "cgTW8vA3pzrPZ7VcRsVSuiaXKPmbY1IqoAr5MBWjHG8=", "Version": "$LATEST", "Environment": { "Variables": { "GREETING": "Hi" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "b9c5af0f-baba-41b3-8fa7-bb902df27f63", "State": "Active", "LastUpdateStatus": "InProgress", "LastUpdateStatusReason": "The function is being created.", "LastUpdateStatusReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "arm64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:ab559f26fdef82cfdfbb2942d07b46f84b45b52d0d760d3d623001ab0218052e" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/greeter" } } ``` 8. Upload the second version of the code: `aws lambda update-function-code --function-name greeter --zip-file fileb://function-v2.zip`. `CodeSize` and `CodeSha256` change, and the greeting stays `Hi`. ```text { "FunctionName": "greeter", "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:greeter", "Runtime": "python3.14", "Role": "arn:aws:iam::123456789012:role/learn-lambda-role", "Handler": "app.handler", "CodeSize": 641, "Description": "", "Timeout": 10, "MemorySize": 256, "LastModified": "2026-10-17T01:43:47.000+0000", "CodeSha256": "OhqqtQSrneSjRDt50w/0TXzs8X0gHT5DMfjK6n62fes=", "Version": "$LATEST", "Environment": { "Variables": { "GREETING": "Hi" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "7e7a5d68-fe32-415f-a32d-be5fae925fc4", "State": "Active", "LastUpdateStatus": "InProgress", "LastUpdateStatusReason": "The function is being created.", "LastUpdateStatusReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "arm64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:ab559f26fdef82cfdfbb2942d07b46f84b45b52d0d760d3d623001ab0218052e" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/greeter" } } ``` 9. Invoke it again: `aws lambda invoke --function-name greeter --payload fileb://event.json response.json`. The status is the same as in step 5. ```text { "StatusCode": 200, "ExecutedVersion": "$LATEST" } ``` 10. Read the new response: `cat response.json; echo`. The greeting is now `Hi`, and the second version of the code adds `"version": 2`. ```text {"message": "Hi, Ada!", "remaining_ms": 10000, "version": 2} ``` 11. Delete the function: `aws lambda delete-function --function-name greeter`. Status code 204 means Lambda deleted the function and has nothing more to say. ```text { "StatusCode": 204 } ``` 12. List the functions again: `aws lambda list-functions`. The Region is empty again, as it was in step 2. ```text { "Functions": [] } ``` ## Command Reference The account ID in every answer is `123456789012`. `LastModified`, `RevisionId`, `RuntimeVersionArn`, and `remaining_ms` change on every real run; the outputs show the values from the recording. Each example starts from an empty Region and runs in `~/greeter`, the folder that holds `function.zip`, `function-v2.zip`, and `event.json`. On your own machine, set `ROLE_ARN` first (see [Create the execution role][create-role]). Search the reference to find a command fast: ### Version and list ```zsh aws --version aws lambda list-functions aws lambda list-functions --query 'Functions[].FunctionName' --output text ``` ```text aws-cli/2.38.0 Python/3.14.6 Linux/7.0.12-linuxkit docker/x86_64.amzn.2023 { "Functions": [] } ``` `--query` filters the answer on your machine with a JMESPath expression, and `--output text` prints bare values ([filter output][cli-filter]). An empty list prints nothing. ### Errors before the function exists ```zsh cd ~/greeter aws lambda get-function --function-name greeter aws lambda invoke --function-name greeter --payload fileb://event.json response.json aws lambda update-function-configuration --function-name greeter --environment 'Variables={GREETING=Hi}' aws lambda update-function-code --function-name greeter --zip-file fileb://function-v2.zip aws lambda delete-function --function-name greeter ``` ```text aws: [ERROR]: An error occurred (ResourceNotFoundException) when calling the GetFunction operation: Function not found: arn:aws:lambda:us-east-1:123456789012:function:greeter Additional error details: Type: User aws: [ERROR]: An error occurred (ResourceNotFoundException) when calling the Invoke operation: Function not found: arn:aws:lambda:us-east-1:123456789012:function:greeter:$LATEST Additional error details: Type: User aws: [ERROR]: An error occurred (ResourceNotFoundException) when calling the UpdateFunctionConfiguration operation: Function not found: arn:aws:lambda:us-east-1:123456789012:function:greeter Additional error details: Type: User aws: [ERROR]: An error occurred (ResourceNotFoundException) when calling the UpdateFunctionCode operation: Function not found: arn:aws:lambda:us-east-1:123456789012:function:greeter Additional error details: Type: User aws: [ERROR]: An error occurred (ResourceNotFoundException) when calling the DeleteFunction operation: Function not found: arn:aws:lambda:us-east-1:123456789012:function:greeter Additional error details: Type: User ``` Each command exits with status 254 and names the missing function by its ARN. `invoke` adds `:$LATEST`, the version it tried to call. ### Create and inspect ```zsh cd ~/greeter aws lambda create-function --function-name greeter --runtime python3.14 --role "$ROLE_ARN" --handler app.handler --architectures arm64 --memory-size 256 --timeout 10 --environment 'Variables={GREETING=Hello}' --zip-file fileb://function.zip aws lambda create-function --function-name greeter --runtime python3.14 --role "$ROLE_ARN" --handler app.handler --architectures arm64 --memory-size 256 --timeout 10 --environment 'Variables={GREETING=Hello}' --zip-file fileb://function.zip aws lambda get-function --function-name greeter --query Configuration.State --output text aws lambda get-function --function-name greeter --query Configuration.Runtime --output text aws lambda get-function --function-name greeter --query 'Configuration.[MemorySize,Timeout]' --output text aws lambda get-function --function-name greeter --query Configuration.Environment.Variables.GREETING --output text aws lambda list-functions --query 'Functions[].FunctionName' --output text aws lambda list-functions --query 'Functions[].[FunctionName,Runtime]' --output text ``` ```text { "FunctionName": "greeter", "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:greeter", "Runtime": "python3.14", "Role": "arn:aws:iam::123456789012:role/learn-lambda-role", "Handler": "app.handler", "CodeSize": 619, "Description": "", "Timeout": 10, "MemorySize": 256, "LastModified": "2026-10-17T01:43:01.782+0000", "CodeSha256": "cgTW8vA3pzrPZ7VcRsVSuiaXKPmbY1IqoAr5MBWjHG8=", "Version": "$LATEST", "Environment": { "Variables": { "GREETING": "Hello" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "7c2e1e5b-14b1-4451-908d-59a1fd3595b0", "State": "Pending", "StateReason": "The function is being created.", "StateReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "arm64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:ab559f26fdef82cfdfbb2942d07b46f84b45b52d0d760d3d623001ab0218052e" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/greeter" } } aws: [ERROR]: An error occurred (ResourceConflictException) when calling the CreateFunction operation: Function already exist: greeter Additional error details: Type: User Active python3.14 256 10 Hello greeter greeter python3.14 ``` A second `create-function` with the same name fails with `ResourceConflictException`. `get-function` with `--query` reads one setting; the text output separates several values with tabs. ### Invoke with a file or an inline payload ```zsh cd ~/greeter aws lambda create-function --function-name greeter --runtime python3.14 --role "$ROLE_ARN" --handler app.handler --architectures arm64 --memory-size 256 --timeout 10 --environment 'Variables={GREETING=Hello}' --zip-file fileb://function.zip aws lambda invoke --function-name greeter --payload fileb://event.json response.json cat response.json; echo aws lambda invoke --function-name greeter --payload '{"name": "Ada"}' response.json aws lambda invoke --function-name greeter --cli-binary-format raw-in-base64-out --payload '{"name": "Ada"}' response.json cat response.json; echo ``` ```text { "FunctionName": "greeter", "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:greeter", "Runtime": "python3.14", "Role": "arn:aws:iam::123456789012:role/learn-lambda-role", "Handler": "app.handler", "CodeSize": 619, "Description": "", "Timeout": 10, "MemorySize": 256, "LastModified": "2026-10-17T01:43:01.782+0000", "CodeSha256": "cgTW8vA3pzrPZ7VcRsVSuiaXKPmbY1IqoAr5MBWjHG8=", "Version": "$LATEST", "Environment": { "Variables": { "GREETING": "Hello" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "7c2e1e5b-14b1-4451-908d-59a1fd3595b0", "State": "Pending", "StateReason": "The function is being created.", "StateReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "arm64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:ab559f26fdef82cfdfbb2942d07b46f84b45b52d0d760d3d623001ab0218052e" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/greeter" } } { "StatusCode": 200, "ExecutedVersion": "$LATEST" } {"message": "Hello, Ada!", "remaining_ms": 9999} aws: [ERROR]: Invalid base64: "{"name": "Ada"}" { "StatusCode": 200, "ExecutedVersion": "$LATEST" } {"message": "Hello, Ada!", "remaining_ms": 9999} ``` AWS CLI v2 reads `--payload` as base64 by default, so the inline JSON fails with `Invalid base64` and exit status 255 before any request leaves the machine. `--cli-binary-format raw-in-base64-out` tells the CLI to send the text as it is ([binary parameters in AWS CLI v2][cli-binary-format]). `fileb://event.json` sends the file's bytes unchanged, so it needs no extra flag. ### Update the settings and the code, then delete ```zsh cd ~/greeter aws lambda create-function --function-name greeter --runtime python3.14 --role "$ROLE_ARN" --handler app.handler --architectures arm64 --memory-size 256 --timeout 10 --environment 'Variables={GREETING=Hello}' --zip-file fileb://function.zip aws lambda update-function-configuration --function-name greeter --environment 'Variables={GREETING=Hi}' aws lambda get-function --function-name greeter --query Configuration.Environment.Variables.GREETING --output text aws lambda invoke --function-name greeter --payload fileb://event.json response.json cat response.json; echo aws lambda update-function-code --function-name greeter --zip-file fileb://function-v2.zip aws lambda get-function --function-name greeter --query Configuration.CodeSha256 --output text aws lambda invoke --function-name greeter --payload fileb://event.json response.json cat response.json; echo aws lambda delete-function --function-name greeter aws lambda delete-function --function-name greeter aws lambda invoke --function-name greeter --payload fileb://event.json response.json ``` ```text { "FunctionName": "greeter", "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:greeter", "Runtime": "python3.14", "Role": "arn:aws:iam::123456789012:role/learn-lambda-role", "Handler": "app.handler", "CodeSize": 619, "Description": "", "Timeout": 10, "MemorySize": 256, "LastModified": "2026-10-17T01:43:01.782+0000", "CodeSha256": "cgTW8vA3pzrPZ7VcRsVSuiaXKPmbY1IqoAr5MBWjHG8=", "Version": "$LATEST", "Environment": { "Variables": { "GREETING": "Hello" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "7c2e1e5b-14b1-4451-908d-59a1fd3595b0", "State": "Pending", "StateReason": "The function is being created.", "StateReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "arm64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:ab559f26fdef82cfdfbb2942d07b46f84b45b52d0d760d3d623001ab0218052e" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/greeter" } } { "FunctionName": "greeter", "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:greeter", "Runtime": "python3.14", "Role": "arn:aws:iam::123456789012:role/learn-lambda-role", "Handler": "app.handler", "CodeSize": 619, "Description": "", "Timeout": 10, "MemorySize": 256, "LastModified": "2026-10-17T01:43:35.000+0000", "CodeSha256": "cgTW8vA3pzrPZ7VcRsVSuiaXKPmbY1IqoAr5MBWjHG8=", "Version": "$LATEST", "Environment": { "Variables": { "GREETING": "Hi" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "b9c5af0f-baba-41b3-8fa7-bb902df27f63", "State": "Active", "LastUpdateStatus": "InProgress", "LastUpdateStatusReason": "The function is being created.", "LastUpdateStatusReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "arm64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:ab559f26fdef82cfdfbb2942d07b46f84b45b52d0d760d3d623001ab0218052e" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/greeter" } } Hi { "StatusCode": 200, "ExecutedVersion": "$LATEST" } {"message": "Hi, Ada!", "remaining_ms": 9999} { "FunctionName": "greeter", "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:greeter", "Runtime": "python3.14", "Role": "arn:aws:iam::123456789012:role/learn-lambda-role", "Handler": "app.handler", "CodeSize": 641, "Description": "", "Timeout": 10, "MemorySize": 256, "LastModified": "2026-10-17T01:43:47.000+0000", "CodeSha256": "OhqqtQSrneSjRDt50w/0TXzs8X0gHT5DMfjK6n62fes=", "Version": "$LATEST", "Environment": { "Variables": { "GREETING": "Hi" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "7e7a5d68-fe32-415f-a32d-be5fae925fc4", "State": "Active", "LastUpdateStatus": "InProgress", "LastUpdateStatusReason": "The function is being created.", "LastUpdateStatusReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "arm64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:ab559f26fdef82cfdfbb2942d07b46f84b45b52d0d760d3d623001ab0218052e" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/greeter" } } OhqqtQSrneSjRDt50w/0TXzs8X0gHT5DMfjK6n62fes= { "StatusCode": 200, "ExecutedVersion": "$LATEST" } {"message": "Hi, Ada!", "remaining_ms": 10000, "version": 2} { "StatusCode": 204 } aws: [ERROR]: An error occurred (ResourceNotFoundException) when calling the DeleteFunction operation: Function not found: arn:aws:lambda:us-east-1:123456789012:function:greeter Additional error details: Type: User aws: [ERROR]: An error occurred (ResourceNotFoundException) when calling the Invoke operation: Function not found: arn:aws:lambda:us-east-1:123456789012:function:greeter:$LATEST Additional error details: Type: User ``` `update-function-configuration` replaces the whole environment, so the new `GREETING` takes effect on the next invoke. `update-function-code` uploads `function-v2.zip`, the same code with `"version": 2` added to the response, and the new `CodeSha256` identifies it. Both answers show `"LastUpdateStatus": "InProgress"`; on your own account, run `aws lambda wait function-updated-v2 --function-name greeter` before the next call. After `delete-function`, every call on `greeter` fails with `ResourceNotFoundException` again. [cli-filter]: https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-filter.html [cli-binary-format]: https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-binaryparam ## Troubleshooting **`Error: Running AWS SAM projects locally requires a container runtime. Do you have Docker or Finch installed and running?`** `sam local invoke` could not reach Docker. Start Docker Desktop or your container runtime, wait until it reports running, and run the command again. **`Error: Unable to locate credentials`** `sam deploy` found no AWS credentials. Run `aws configure`, or `aws sso login` for an SSO profile, confirm with `aws sts get-caller-identity`, then deploy again. **`Handler 'lambda_handler' missing on module 'app'` (`Runtime.HandlerNotFound`)** The `Handler` value in the template does not match the function name in your code. `app.lambda_handler` looks for `lambda_handler` in `app.py`; rename one side so they match, then run `sam build` again. **`Function 'GreeterFunction' timed out after 10 seconds` (locally) or `Status: timeout` on the `REPORT` line (CloudWatch)** The handler ran longer than `Timeout`. Raise the timeout if the work is legitimate, or find the slow call. A function that waits on a network call with no timeout of its own will hang until Lambda kills it. **Your code change does not show up in `sam local invoke`** `sam local invoke` runs the copy in `.aws-sam/build`. Run `sam build` after every change. ## Structuring a Real Function The sources behind this guide agree on a short list of habits for functions that live longer than a tutorial: * **Split the handler from the logic.** Keep the handler thin, parse the event, then call plain functions you can unit test without Lambda, as the [Reddit thread][reddit-wellmade] recommends. * **Prefer configuration over code.** Use event filtering on event source mappings so your function never sees events it would discard, and use native service integrations instead of glue functions ([Mao][mao-guide]). * **Validate input.** Events come from other systems; check the shape before you trust it. * **Test first.** One commenter in the thread put it plainly: the most important thing is your tests. ## Where Lambda Is Going Three newer options address the jobs Event Functions handle poorly: * **Lambda Web Functions** launched on October 12, 2026. Your code is an ordinary web server, such as Express or Hono, listening on a port. One execution environment serves 64 concurrent requests by default and up to 128 when configured, and the function scales to zero ([Web Functions launch post][web-functions-blog], [Web Functions docs][dg-web-functions]). At launch, it supports Node.js 24 on Arm in five Regions. * **Lambda durable functions** run multi-step workflows for up to a year with checkpoints and replay ([durable functions][dg-durable]). They answer the complaint in Qian Li's [post on Lambda's stateless nature][qian-li-post], written to introduce her company's DBOS product, that stateful tasks such as asynchronous external API calls are hard to run on Lambda. * **Lambda MicroVMs** give AI agents, development environments, and CI jobs a full operating system with VM-level isolation ([MicroVMs][dg-microvms]). ## Learn AWS Lambda: Beyond the Basics You've covered the essentials. Start with the best practices talk, build two or three functions of your own, then pick a book to go deep. 📹 **Video** * [Best practices for serverless developers (CNS403)][video-cns403], AWS re:Invent 2025. * [From Trigger to Execution: The Journey of Events in AWS Lambda (CNS423)][video-cns423], AWS re:Invent 2025, for how Lambda works internally. * [Lambda performance tuning][office-hours-tuning], Serverless Office Hours on the Serverless Land channel (2024). * [AWS Lambda Introduction][video-babd], Be A Better Dev (2022), a 13-minute primer for complete beginners. 🔊 **Audio** * [AWS Bites episode 120, "Lambda Best Practices"][aws-bites-120] (2024), covering cold starts, the three invocation types, logging, and security. 📚 **Books** * [*Serverless Development on AWS*][book-brisals] by Sheen Brisals and Luke Hedger (O'Reilly, 2024), for teams building production serverless systems. * [*Serverless Architectures on AWS*, Second Edition][book-sbarski] by Peter Sbarski, Yan Cui, and Ajay Nair (Manning, 2022), for architecture patterns around Lambda. 🌐 **Online** * [AWS Lambda Developer Guide][dg-welcome], the source for every limit in this guide. * [Best practices for working with Lambda functions][dg-best-practices]. * [Serverless Land][serverless-land], with patterns, workshops, and the Office Hours archive. * [Powertools for AWS Lambda][powertools]. * [AWS Lambda Power Tuning][power-tuning]. * [AWS SAM Developer Guide][sam-docs]. ## Related Content * [Learn Amazon S3][learn-s3] covers the storage service that triggers many Lambda functions. * [Learn Amazon IAM][learn-iam] explains the permissions every function's execution role needs. * [Cloud Providers Guide][cloud-providers] puts AWS next to Azure and Google Cloud. * [Fundamentals of Monitoring and Observability][fundamentals-observability] goes deeper on logs, metrics, and traces. * [Learn Python][learn-python] if the handler code above is new to you. * [Fundamentals of AWS][fundamentals-aws] explains Regions, IAM, shared responsibility, and the core AWS services around Lambda. * [Learn Zsh][learn-zsh] covers zsh, the shell the AWS CLI practice shell runs, including completion and history. ## References * [Lambda Event Functions][dg-event-functions], [execution environment lifecycle][dg-lifecycle], [Python handler][dg-python-handler], and [Lambda quotas][dg-limits], for the execution model and limits. * [Memory configuration][dg-memory], [timeout][dg-timeout], and [ephemeral storage][dg-tmp], for configuration ranges and defaults. * [Retries][dg-retries], [asynchronous invocation][dg-async], [event source mappings][dg-esm], and [SQS error handling][dg-sqs-errors], for invocation types and failure handling. * [Reserved concurrency][dg-concurrency], [provisioned concurrency][dg-provisioned], and [SnapStart][dg-snapstart], for concurrency controls. * [Container images][dg-images], [Python container images][dg-python-image], and [supported runtimes][dg-runtimes], for packaging. * [Lambda pricing][pricing] and the [Graviton2 launch post][graviton], for the cost model. * [sam local invoke][sam-local-invoke] and the [AWS SAM Developer Guide][sam-docs], for the hands-on commands. * [AWS CDK bootstrapping][cdk-bootstrapping], [Bootstrap your environment][cdk-bootstrap-env], [cdk deploy][cdk-deploy], [cdk destroy][cdk-destroy], and the [Function construct][cdk-lambda-function], for the CDK deployment. * [X-Ray SDK and daemon support timeline][xray-eos], for the move to OpenTelemetry. * [Web Functions launch post][web-functions-blog], [Web Functions docs][dg-web-functions], [durable functions][dg-durable], and [MicroVMs][dg-microvms], for the newer Lambda options. * George Mao, [Development Best Practices (The Ultimate Guide to AWS Lambda Development, Chapter 1)][mao-guide] (2024). * r/aws, [So what does a well-made AWS Lambda in Python actually look like?][reddit-wellmade] (2023). * Luca Mezzalira, [post on Lambda performance tuning][mezzalira-post] (2024). * Darryl Ruggles, [Powertools for AWS Lambda: Best Practices By Default][ruggles-powertools] (2026). * Qian Li, [post on Lambda's stateless nature][qian-li-post] (2024). [dg-event-functions]: https://docs.aws.amazon.com/lambda/latest/dg/lambda-functions-chapter.html [dg-lifecycle]: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html [dg-python-handler]: https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html [dg-limits]: https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html [dg-memory]: https://docs.aws.amazon.com/lambda/latest/dg/configuration-memory.html [dg-timeout]: https://docs.aws.amazon.com/lambda/latest/dg/configuration-timeout.html [dg-tmp]: https://docs.aws.amazon.com/lambda/latest/dg/configuration-ephemeral-storage.html [dg-retries]: https://docs.aws.amazon.com/lambda/latest/dg/invocation-retries.html [dg-async]: https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html [dg-esm]: https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventsourcemapping.html [dg-sqs-errors]: https://docs.aws.amazon.com/lambda/latest/dg/services-sqs-errorhandling.html [dg-concurrency]: https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html [dg-provisioned]: https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html [dg-snapstart]: https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html [dg-images]: https://docs.aws.amazon.com/lambda/latest/dg/images-create.html [dg-python-image]: https://docs.aws.amazon.com/lambda/latest/dg/python-image.html [dg-runtimes]: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html [dg-best-practices]: https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html [dg-welcome]: https://docs.aws.amazon.com/lambda/latest/dg/welcome.html [dg-durable]: https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html [dg-web-functions]: https://docs.aws.amazon.com/lambda/latest/dg/web-functions.html [dg-microvms]: https://docs.aws.amazon.com/lambda/latest/dg/lambda-microvms-guide.html [pricing]: https://aws.amazon.com/lambda/pricing/ [graviton]: https://aws.amazon.com/blogs/aws/aws-lambda-functions-powered-by-aws-graviton2-processor-run-your-functions-on-arm-and-get-up-to-34-better-price-performance/ [sam-docs]: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html [sam-local-invoke]: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html [xray-eos]: https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-daemon-timeline.html [powertools]: https://docs.aws.amazon.com/powertools/ [power-tuning]: https://github.com/alexcasalboni/aws-lambda-power-tuning [web-functions-blog]: https://aws.amazon.com/blogs/aws/announcing-aws-lambda-web-functions-for-fast-consistent-web-app-performance-anywhere/ [mao-guide]: https://georgemao.medium.com/development-best-practices-the-ultimate-guide-to-aws-lambda-development-chapter-1-1ba3d3c219b9 [reddit-wellmade]: https://www.reddit.com/r/aws/comments/16pesw1/so_what_does_a_wellmade_aws_lambda_in_python/ [mezzalira-post]: https://www.linkedin.com/posts/lucamezzalira_lambda-performance-tuning-serverless-office-activity-7165808858475532288-YPGp/ [ruggles-powertools]: https://darryl-ruggles.cloud/powertools-for-aws-lambda-best-practices-by-default/ [qian-li-post]: https://www.linkedin.com/posts/qianli-dev_aws-lambda-is-an-incredible-technology-it-activity-7276323367031394305-xAjT/ [office-hours-tuning]: https://www.youtube.com/watch?v=owuv7_fCamo [video-cns403]: https://www.youtube.com/watch?v=tMvwM_KXNPA [video-cns423]: https://www.youtube.com/watch?v=4zFJ3zDWgeM [video-babd]: https://www.youtube.com/watch?v=UsaiOEFdfs0 [aws-bites-120]: https://dev.to/awsbites/120-lambda-best-practices [book-brisals]: https://www.oreilly.com/library/view/serverless-development-on/9781098141929/ [book-sbarski]: https://www.manning.com/books/serverless-architectures-on-aws-second-edition [serverless-land]: https://serverlessland.com/ [learn-s3]: https://jeffbailey.us/blog/2025/09/19/learn-amazon-s3/ [learn-iam]: https://jeffbailey.us/blog/2025/09/19/learn-amazon-iam/ [cloud-providers]: https://jeffbailey.us/blog/2020/06/13/cloud-providers/ [fundamentals-observability]: https://jeffbailey.us/blog/2025/11/16/fundamentals-of-monitoring-and-observability/ [learn-python]: https://jeffbailey.us/blog/2020/05/26/learn-python/ [fundamentals-aws]: https://jeffbailey.us/blog/2026/03/06/fundamentals-of-aws/ [learn-zsh]: https://jeffbailey.us/blog/2026/10/06/learn-zsh/ [create-role]: #create-the-execution-role