TerraformPilot

Terraform

AWS Lambda Functions with Terraform - Complete Guide

Deploy AWS Lambda functions with Terraform. Complete guide with IAM roles, API Gateway triggers, S3 triggers, layers, environment variables, and VPC...

LLuca Berton1 min read

Quick Answer

#
resource "aws_lambda_function" "api" {
  filename         = "lambda.zip"
  function_name    = "my-api"
  role             = aws_iam_role.lambda.arn
  handler          = "index.handler"
  runtime          = "nodejs20.x"
  source_code_hash = filebase64sha256("lambda.zip")
}

Prerequisites

#
  • AWS account with Lambda permissions
  • Terraform 1.5+
  • Application code packaged as a .zip file

IAM Role for Lambda

#
data "aws_iam_policy_document" "lambda_assume" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
  }
}
 
resource "aws_iam_role" "lambda" {
  name               = "${var.project}-lambda-role"
  assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}
 
resource "aws_iam_role_policy_attachment" "lambda_basic" {
  role       = aws_iam_role.lambda.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

Lambda Function

#
data "archive_file" "lambda" {
  type        = "zip"
  source_dir  = "${path.module}/src"
  output_path = "${path.module}/lambda.zip"
}
 
resource "aws_lambda_function" "api" {
  filename         = data.archive_file.lambda.output_path
  function_name    = "${var.project}-${var.environment}-api"
  role             = aws_iam_role.lambda.arn
  handler          = "index.handler"
  runtime          = "nodejs20.x"
  timeout          = 30
  memory_size      = 256
  source_code_hash = data.archive_file.lambda.output_base64sha256
 
  environment {
    variables = {
      ENVIRONMENT = var.environment
      TABLE_NAME  = aws_dynamodb_table.main.name
    }
  }
 
  tracing_config {
    mode = "Active"  # X-Ray tracing
  }
}

API Gateway Trigger

#
resource "aws_apigatewayv2_api" "main" {
  name          = "${var.project}-api"
  protocol_type = "HTTP"
}
 
resource "aws_apigatewayv2_stage" "default" {
  api_id      = aws_apigatewayv2_api.main.id
  name        = "$default"
  auto_deploy = true
}
 
resource "aws_apigatewayv2_integration" "lambda" {
  api_id                 = aws_apigatewayv2_api.main.id
  integration_type       = "AWS_PROXY"
  integration_uri        = aws_lambda_function.api.invoke_arn
  payload_format_version = "2.0"
}
 
resource "aws_apigatewayv2_route" "api" {
  api_id    = aws_apigatewayv2_api.main.id
  route_key = "ANY /{proxy+}"
  target    = "integrations/${aws_apigatewayv2_integration.lambda.id}"
}
 
resource "aws_lambda_permission" "apigw" {
  statement_id  = "AllowAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.api.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${aws_apigatewayv2_api.main.execution_arn}/*/*"
}
 
output "api_url" {
  value = aws_apigatewayv2_stage.default.invoke_url
}

S3 Event Trigger

#
resource "aws_lambda_permission" "s3" {
  statement_id  = "AllowS3"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.processor.function_name
  principal     = "s3.amazonaws.com"
  source_arn    = aws_s3_bucket.uploads.arn
}
 
resource "aws_s3_bucket_notification" "trigger" {
  bucket = aws_s3_bucket.uploads.id
 
  lambda_function {
    lambda_function_arn = aws_lambda_function.processor.arn
    events              = ["s3:ObjectCreated:*"]
    filter_prefix       = "uploads/"
    filter_suffix       = ".csv"
  }
 
  depends_on = [aws_lambda_permission.s3]
}

Lambda Layers

#
resource "aws_lambda_layer_version" "dependencies" {
  filename            = "layer.zip"
  layer_name          = "${var.project}-dependencies"
  compatible_runtimes = ["nodejs20.x"]
  source_code_hash    = filebase64sha256("layer.zip")
}
 
resource "aws_lambda_function" "api" {
  # ...
  layers = [aws_lambda_layer_version.dependencies.arn]
}

Lambda in VPC

#
resource "aws_lambda_function" "private" {
  # ...
  vpc_config {
    subnet_ids         = aws_subnet.private[*].id
    security_group_ids = [aws_security_group.lambda.id]
  }
}
 
# Lambda in VPC needs this additional policy
resource "aws_iam_role_policy_attachment" "lambda_vpc" {
  role       = aws_iam_role.lambda.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}

Configuration Options

#
SettingDefaultRecommendation
timeout3s30s for API, 900s for batch
memory_size128 MB256-512 MB for most APIs
runtimenodejs20.x, python3.12
architecturesx86_64arm64 for 20% cost savings
#

Conclusion

#

Package code with archive_file, create an IAM role with least privilege, and connect triggers (API Gateway, S3, SQS). Use source_code_hash to trigger redeployments when code changes. Use arm64 architecture for cost savings and Lambda layers for shared dependencies.

#Terraform#AWS#Lambda#Serverless#Infrastructure as Code

Share this article