Docs / Terraform

Terraform

Use your existing Terraform configurations with LocalEmu. Point the AWS provider at localhost:4566 and run terraform apply as usual.

Provider Configuration

Override the AWS provider endpoints to point at LocalEmu. Skip credential validation and metadata checks since they are not needed locally.

provider.tf
provider "aws" {
  region                      = "us-east-1"
  access_key                  = "AKIAIOSFODNN7EXAMPLE"
  secret_key                  = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    s3             = "http://localhost:4566"
    dynamodb       = "http://localhost:4566"
    lambda         = "http://localhost:4566"
    sqs            = "http://localhost:4566"
    sns            = "http://localhost:4566"
    iam            = "http://localhost:4566"
    sts            = "http://localhost:4566"
    cloudwatch     = "http://localhost:4566"
    cloudformation = "http://localhost:4566"
    secretsmanager = "http://localhost:4566"
    stepfunctions  = "http://localhost:4566"
    kinesis        = "http://localhost:4566"
    apigateway     = "http://localhost:4566"
    ec2            = "http://localhost:4566"
    route53        = "http://localhost:4566"
  }
}

Add more endpoint entries as needed. Every service uses the same http://localhost:4566 URL.

S3 Bucket

s3.tf
resource "aws_s3_bucket" "data" {
  bucket = "my-data-bucket"
}

resource "aws_s3_bucket_versioning" "data" {
  bucket = aws_s3_bucket.data.id
  versioning_configuration {
    status = "Enabled"
  }
}

DynamoDB Table

dynamodb.tf
resource "aws_dynamodb_table" "users" {
  name         = "Users"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "userId"

  attribute {
    name = "userId"
    type = "S"
  }

  tags = {
    Environment = "local"
  }
}

Lambda Function

The archive_file data source reads src/handler.py from your working directory, so create it first:

Prerequisite
$ mkdir -p src build
$ cat > src/handler.py <<'EOF'
def handler(event, context):
    return {"statusCode": 200, "body": "hello from Terraform"}
EOF
lambda.tf
data "archive_file" "lambda_zip" {
  type        = "zip"
  source_file = "src/handler.py"
  output_path = "build/function.zip"
}

resource "aws_lambda_function" "api" {
  function_name    = "api-handler"
  filename         = data.archive_file.lambda_zip.output_path
  source_code_hash = data.archive_file.lambda_zip.output_base64sha256
  handler          = "handler.handler"
  runtime          = "python3.12"
  role             = "arn:aws:iam::000000000000:role/role"

  environment {
    variables = {
      TABLE_NAME = aws_dynamodb_table.users.name
    }
  }
}

SQS Queue

sqs.tf
resource "aws_sqs_queue" "tasks" {
  name                       = "tasks"
  visibility_timeout_seconds = 30
  message_retention_seconds  = 86400
}

resource "aws_sqs_queue" "tasks_dlq" {
  name = "tasks-dlq"
}

Full Working Example

A complete Terraform configuration that creates an S3 bucket, a DynamoDB table, an SQS queue, and a Lambda function wired together. Create the Lambda source file first so the archive_file data source has something to read:

Prerequisite
$ mkdir -p src build
$ cat > src/processor.py <<'EOF'
def handler(event, context):
    return {"statusCode": 200, "processed": True}
EOF
main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region                      = "us-east-1"
  access_key                  = "AKIAIOSFODNN7EXAMPLE"
  secret_key                  = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    s3             = "http://localhost:4566"
    dynamodb       = "http://localhost:4566"
    lambda         = "http://localhost:4566"
    sqs            = "http://localhost:4566"
    iam            = "http://localhost:4566"
  }
}

resource "aws_s3_bucket" "uploads" {
  bucket = "uploads"
}

resource "aws_dynamodb_table" "metadata" {
  name         = "FileMetadata"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "fileId"

  attribute {
    name = "fileId"
    type = "S"
  }
}

resource "aws_sqs_queue" "processing" {
  name = "file-processing"
}

data "archive_file" "processor" {
  type        = "zip"
  source_file = "src/processor.py"
  output_path = "build/processor.zip"
}

resource "aws_lambda_function" "processor" {
  function_name    = "file-processor"
  filename         = data.archive_file.processor.output_path
  source_code_hash = data.archive_file.processor.output_base64sha256
  handler          = "processor.handler"
  runtime          = "python3.12"
  role             = "arn:aws:iam::000000000000:role/role"

  environment {
    variables = {
      TABLE_NAME = aws_dynamodb_table.metadata.name
      QUEUE_URL  = aws_sqs_queue.processing.url
    }
  }
}

Apply

Terminal
$ terraform init
$ terraform plan
$ terraform apply -auto-approve
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

# Verify with awsemu
$ awsemu s3 ls
2026-04-06 10:00:00 uploads

$ awsemu dynamodb list-tables
{"TableNames": ["FileMetadata"]}