Skip to main content
Deploy an AWS Lambda Function with Terraform

Deploy an AWS Lambda Function with Terraform

Key Takeaway

Step-by-step guide to deploying serverless AWS Lambda functions using Terraform with IAM roles, API Gateway triggers, and CloudWatch logging.

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

  1. Use source_code_hash to detect code changes automatically
  2. Set appropriate timeout and memory — start small, scale up based on metrics
  3. Enable X-Ray tracing for debugging distributed systems
  4. Set log retention to control CloudWatch costs
  5. Use environment variables for configuration, never hardcode secrets
  6. Tag everything for cost tracking and organization

Hands-On Courses

Learn by doing with interactive courses on CopyPasteLearn:

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.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.