Table of Contents
Introduction
AWS Lambda lets you run code without provisioning servers. Combined with Terraform, you can version-control your entire serverless infrastructure. This guide walks through creating a production-ready Lambda function with proper IAM permissions, API Gateway integration, and monitoring.
Prerequisites
- AWS account with appropriate permissions
- Terraform >= 1.5 installed
- Basic knowledge of Python or Node.js
Project Structure
lambda-project/
├── main.tf
├── variables.tf
├── outputs.tf
├── lambda/
│ └── handler.py
└── terraform.tfvars
Creating the Lambda Function Code
# lambda/handler.py
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info(f"Received event: {json.dumps(event)}")
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({
'message': 'Hello from Terraform-managed Lambda!',
'input': event
})
}
Terraform Configuration
IAM Role for Lambda
resource "aws_iam_role" "lambda_exec" {
name = "lambda-exec-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" "lambda_basic" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
role = aws_iam_role.lambda_exec.name
}
Lambda Function Resource
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "${path.module}/lambda"
output_path = "${path.module}/lambda.zip"
}
resource "aws_lambda_function" "api" {
filename = data.archive_file.lambda_zip.output_path
function_name = var.function_name
role = aws_iam_role.lambda_exec.arn
handler = "handler.lambda_handler"
runtime = "python3.12"
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
timeout = 30
memory_size = 256
environment {
variables = {
ENVIRONMENT = var.environment
LOG_LEVEL = "INFO"
}
}
tracing_config {
mode = "Active"
}
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
API Gateway Integration
resource "aws_apigatewayv2_api" "lambda_api" {
name = "${var.function_name}-api"
protocol_type = "HTTP"
cors_configuration {
allow_origins = ["*"]
allow_methods = ["GET", "POST", "OPTIONS"]
allow_headers = ["Content-Type"]
max_age = 300
}
}
resource "aws_apigatewayv2_stage" "default" {
api_id = aws_apigatewayv2_api.lambda_api.id
name = "$default"
auto_deploy = true
access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_gw.arn
format = jsonencode({
requestId = "$context.requestId"
ip = "$context.identity.sourceIp"
requestTime = "$context.requestTime"
httpMethod = "$context.httpMethod"
routeKey = "$context.routeKey"
status = "$context.status"
protocol = "$context.protocol"
responseLength = "$context.responseLength"
})
}
}
resource "aws_apigatewayv2_integration" "lambda" {
api_id = aws_apigatewayv2_api.lambda_api.id
integration_type = "AWS_PROXY"
integration_uri = aws_lambda_function.api.invoke_arn
integration_method = "POST"
}
resource "aws_apigatewayv2_route" "default" {
api_id = aws_apigatewayv2_api.lambda_api.id
route_key = "GET /"
target = "integrations/${aws_apigatewayv2_integration.lambda.id}"
}
resource "aws_lambda_permission" "api_gw" {
statement_id = "AllowAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.api.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.lambda_api.execution_arn}/*/*"
}
CloudWatch Logging
resource "aws_cloudwatch_log_group" "lambda" {
name = "/aws/lambda/${aws_lambda_function.api.function_name}"
retention_in_days = 14
}
resource "aws_cloudwatch_log_group" "api_gw" {
name = "/aws/apigateway/${aws_apigatewayv2_api.lambda_api.name}"
retention_in_days = 14
}
Variables and Outputs
# variables.tf
variable "function_name" {
default = "my-api-function"
}
variable "environment" {
default = "production"
}
# outputs.tf
output "api_endpoint" {
value = aws_apigatewayv2_stage.default.invoke_url
}
output "function_name" {
value = aws_lambda_function.api.function_name
}
Deployment
terraform init
terraform plan
terraform apply
After deployment, Terraform outputs the API endpoint URL. Test it with:
curl $(terraform output -raw api_endpoint)
Best Practices
- Use
source_code_hashto detect code changes automatically - Set appropriate timeout and memory — start small, scale up based on metrics
- Enable X-Ray tracing for debugging distributed systems
- Set log retention to control CloudWatch costs
- Use environment variables for configuration, never hardcode secrets
- Tag everything for cost tracking and organization
Conclusion
Terraform makes Lambda deployments repeatable and version-controlled. By combining Lambda with API Gateway, IAM, and CloudWatch through Terraform, you get a complete serverless stack that’s easy to maintain, replicate across environments, and roll back when needed.

