Lambda Function with IAM Execution Role
Scenario
Your team wants to run a small Python function that logs a greeting message whenever it is invoked. Rather than uploading a zip file manually, you want Terraform to package and deploy the function code automatically from a local file.
Provision the Lambda function, its IAM execution role (with basic logging permissions), and verify it runs by invoking it from the CLI.
- Write a small Python handler in a local lambda_src/handler.py file.
- Use the archive_file data source to zip the source directory automatically.
- Create an aws_iam_role with a trust policy for lambda.amazonaws.com.
- Attach the AWSLambdaBasicExecutionRole managed policy so the function can write to CloudWatch Logs.
- Create an aws_lambda_function pointing to the zip file, using Python 3.12 runtime.
- Output the function ARN and invoke URL for testing.
Additional Context
The archive_file data source (from the hashicorp/archive provider) creates a zip of your source directory each time Terraform runs. Terraform tracks the output_base64sha256 of the archive — if the source code changes, the hash changes, which triggers a Lambda function update on the next apply. This is a common zero-friction deployment pattern.
lambda_src/handler.py
# lambda_src/handler.py (Python source — not HCL)
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
name = event.get("name", "World")
message = f"Hello, {name}! Welcome to Terraform + Lambda."
logger.info(message)
return {
"statusCode": 200,
"body": json.dumps({"message": message})
}versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
archive = { source = "hashicorp/archive", version = "~> 2.4" }
}
}variables.tf
variable "aws_region" { type = string; default = "us-east-1" }
variable "function_name" { type = string; default = "hello-world" }
variable "environment" { type = string; default = "dev" }main.tf
# main.tf
provider "aws" {
region = var.aws_region
}
# Archive the lambda_src/ directory into a zip file.
# Terraform re-zips on every plan. If the source changes, output_base64sha256
# changes, which triggers a new Lambda deployment automatically.
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "${path.module}/lambda_src"
output_path = "${path.module}/lambda.zip"
}
# Trust policy — allow the Lambda service to assume this role.
data "aws_iam_policy_document" "lambda_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
# Lambda execution role.
resource "aws_iam_role" "lambda" {
name = "${var.function_name}-role"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
tags = { ManagedBy = "terraform", Environment = var.environment }
}
# Attach AWS managed policy: allows writing logs to CloudWatch.
resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# The Lambda function itself.
resource "aws_lambda_function" "hello" {
function_name = var.function_name
runtime = "python3.12"
handler = "handler.lambda_handler" # filename.function_name
role = aws_iam_role.lambda.arn
# Point to the zip created by the archive_file data source.
filename = data.archive_file.lambda_zip.output_path
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
timeout = 30 # seconds
memory_size = 128 # MB
environment {
variables = {
ENV = var.environment
}
}
tags = { ManagedBy = "terraform", Environment = var.environment }
# Ensure the role exists before the function is created.
depends_on = [aws_iam_role_policy_attachment.lambda_logs]
}outputs.tf
output "function_arn" { value = aws_lambda_function.hello.arn }
output "function_name" { value = aws_lambda_function.hello.function_name }Workflow Commands
mkdir -p lambda_src
# (create lambda_src/handler.py with the Python code above)
terraform init
terraform apply
# Invoke the function and see the response
aws lambda invoke \
--function-name $(terraform output -raw function_name) \
--payload '{"name":"Terraform"}' \
--cli-binary-format raw-in-base64-out \
response.json
cat response.json
# {"message": "Hello, Terraform! Welcome to Terraform + Lambda."}
terraform destroyApply complete! Resources: 3 added, 0 changed, 0 destroyed. Outputs: function_arn = "arn:aws:lambda:us-east-1:123456789012:function:hello-world" function_name = "hello-world"

