Create Lambda
Scaffold production-ready AWS Lambda functions using the terraform-aws-lambda Terraform module.
When to use this skill
- User wants to create a new Lambda function
- User wants to scaffold a Lambda project with Terraform
- User is setting up serverless infrastructure on AWS with Terraform
- User asks how to use the
moritzzimmer/lambda/awsmodule
Gather requirements
Before generating anything, establish what the user needs. If they provided
arguments via /terraform-lambda, parse them. Otherwise ask concisely:
- Function name (required) — must be a valid Lambda function name
- Runtime — Go, Rust, Python, Java, .NET, Node.js, or container image (default: Python)
- Event source — what triggers the function: SQS, SNS, Kinesis, DynamoDB Streams, CloudWatch Events/schedule, API Gateway, S3, or none (default: none)
- Extra features — VPC, EFS, X-Ray tracing, Lambda Insights, SSM parameters, CloudWatch log retention, snap_start (Java only)
- CI/CD pipeline — does the user want CodeDeploy-based deployments? If so:
- S3 or ECR source?
- S3 bucket: Does an S3 bucket already exist (reference via
datasource or bucket name), or should the skill create a new one inline? This matters for the Terraform setup — ask explicitly. - Deployment strategy: canary, linear, or all-at-once?
- Auto-rollback on failure?
- Target directory — where to create the project (default:
./<function-name>/)
If the user gives a one-liner like "create a Python Lambda triggered by SQS called order-processor", extract all info from that — don't ask again for things already stated.
Generate the project
Directory layout
<function-name>/
├── Makefile # Build and deploy
├── terraform/
│ ├── main.tf # Module usage + event source resources
│ ├── variables.tf # Environment-specific variables
│ ├── outputs.tf # Useful outputs from the module
│ ├── versions.tf # Terraform and provider version constraints
│ └── provider.tf # AWS provider configuration
└── <source code> # Layout is runtime-specific (see references/runtimes.md)
Source code location varies by runtime — Go puts main.go and go.mod at the
project root (idiomatic Go), Rust uses src/ with a cargo lambda new structure,
Python uses app/ at the project root, .NET puts source files at root, Java uses
the standard Gradle src/main/java/ layout. Always consult
references/runtimes.md for the correct layout.
Terraform files
versions.tf — pin providers:
terraform {
required_version = ">= 1.5.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.0"
}
}
}
provider.tf:
provider "aws" {
region = var.region
}
variables.tf — always include region, add others as needed:
variable "region" {
description = "AWS region for all resources"
type = string
default = "eu-central-1"
}
outputs.tf — expose the most useful module outputs:
output "function_arn" {
value = module.<function_name>.arn
}
output "function_name" {
value = module.<function_name>.function_name
}
output "role_arn" {
value = module.<function_name>.role_arn
}
output "invoke_arn" {
value = module.<function_name>.invoke_arn
}
main.tf — the module block plus any event source resources. Key rules:
- Module source:
"moritzzimmer/lambda/aws", version:"~> 8.6" - Alphabetical attribute ordering inside the module block
- Guard
source_code_hashwithfileexists()soterraform validateworks without build artifacts:source_code_hash = fileexists(local.artifact) ? filebase64sha256(local.artifact) : null - Default architecture:
["arm64"](Graviton — better price/performance) - Runtime versions: Do not rely on your training data for Lambda runtime
identifiers — they go stale quickly. Before generating code, look up the
latest runtime by searching the web for "AWS Lambda supported runtimes" or
checking
https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html. If you cannot verify, leave a# TODO: verify this is the latest runtimecomment next to the runtime value so the user knows to check. - Always include
tags = { managed_by = "terraform" }
For runtime-specific settings, event source patterns, and the full variable reference, consult the reference files:
- references/runtimes.md — runtime configs, Makefile templates, and sample function code for each supported language
- references/event-sources.md — how to wire up each event source type with correct Terraform resources
- references/variables.md — full variable reference grouped by feature area, with defaults and IAM implications
- references/deployment.md — CodePipeline/CodeDeploy CI/CD pipeline setup using the deployment submodule
CI/CD with the deployment submodule
When the user wants CodeDeploy-based deployments, the setup changes:
- The main module uses S3-based packaging (
s3_bucket/s3_key) instead of localfilename, and setsignore_external_function_updates = true - An
aws_s3_objectresource uploads the initial build artifact to S3, and the main module referencess3_object_versionfrom it — this ensures the zip exists before the Lambda is created (without this, first apply fails withNoSuchKey). Uselifecycle { ignore_changes = [etag] }so CodePipeline deployments don't conflict. - A Lambda alias is created with
lifecycle { ignore_changes = [function_version] }— CodeDeploy manages version shifts, not Terraform - The deployment submodule (
moritzzimmer/lambda/aws//modules/deployment) creates a CodePipeline + CodeDeploy pipeline
For the full pattern with examples (canary, rollback, alarms), see references/deployment.md.
Important guardrails
These matter because getting them wrong causes hard-to-debug issues:
fileexists() guard: Always wrap
filebase64sha256()— without it,terraform validateandterraform planfail when the zip hasn't been built yet (e.g., in CI before the build step).arm64 default: Use
architectures = ["arm64"]unless the user has a reason for x86_64 (e.g., native dependencies that don't support ARM). Graviton is ~20% cheaper and generally faster for Lambda.Memory defaults by runtime: Go/Rust/Node.js: 128MB. Python/.NET: 256MB. Java: 512MB. These reflect typical cold-start and memory needs — the user can always override.
snap_start is Java-only: Setting it for other runtimes silently does nothing useful. Only suggest it when runtime is Java.
S3 bucket: use
.bucket, not.id: When passing an S3 bucket to the deployment module, always useaws_s3_bucket.x.bucket(the name string), notaws_s3_bucket.x.id. The.idattribute can cause "Invalid count argument" errors because the deployment module usescount = var.s3_bucket != ""at plan time.lambda_at_edge + VPC are incompatible: Lambda@Edge runs at CloudFront edge locations and cannot access a VPC.
reserved_concurrent_executions = 0 disables the function: If the user wants to limit concurrency, suggest a positive number. Zero means "cannot run."
IAM is automatic: The module attaches the right IAM policies based on which features are enabled (VPC, tracing, SSM, event sources, logs). Don't create separate IAM resources for these — it's already handled.
After generation
Verify the generated code builds. After writing all files, run make package
(or the equivalent build command) to confirm the project compiles and packages
successfully. If it fails, fix the issue before presenting the result to the
user. Common causes: missing using/import statements, wrong package names
in build files, or mismatched handler signatures.
Once the files are written:
- Suggest
make helpto see available targets make tfrunsterraform init+terraform planmake tf MODE=applyto deploymake packageto just build the artifact without deploying
If the user wants to add features later (like adding an SQS trigger to an existing function), read the current Terraform config and modify it — don't regenerate from scratch.
Source: moritzzimmer/terraform-aws-lambda — distributed by TomeVault.