AWS SAM Template Example

Deploy an AWS SAM template.yaml with Terraform — no migration, no rewrite

What you'll build

The same HTTP API as the Basic Service example, but driven by an AWS SAM template.yaml instead of a serverless.yml. sls.tf reads the SAM template directly, so the only change is config_format = "sam" — everything else (Lambda, IAM, API Gateway, DynamoDB) is provisioned the same way.

Project structure

my-api/
  src/
    list.js
    create.js
  template.yaml        # AWS SAM template
  main.tf
  outputs.tf

template.yaml

A standard SAM template. Globals, Parameters, and CloudFormation intrinsics such as !Ref and !Sub are resolved at plan time.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Items API (SAM)

Parameters:
  Stage:
    Type: String
    Default: dev

Globals:
  Function:
    Runtime: nodejs20.x
    MemorySize: 256
    Timeout: 10
    Environment:
      Variables:
        TABLE_NAME: !Sub "items-${Stage}"

Resources:
  ListItems:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/list.handler
      Description: List all items
      Events:
        ListApi:
          Type: Api
          Properties:
            Path: /items
            Method: get

  CreateItem:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/create.handler
      Description: Create a new item
      Events:
        CreateApi:
          Type: Api
          Properties:
            Path: /items
            Method: post

  ItemsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub "items-${Stage}"
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH

main.tf

Point the module at template.yaml and set config_format = "sam". Override template Parameters from Terraform with sam_template_parameters:

terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

module "serverless" {
  source = "git::https://github.com/sls-tf/sls.tf.git?ref=v0.3.16"

  config_path   = "${path.module}/template.yaml"
  config_format = "sam"

  # Override the template's Parameters Default values
  sam_template_parameters = {
    Stage = "prod"
  }
}

outputs.tf

Outputs are identical regardless of config format — SAM logical IDs become the function keys:

output "api_url" {
  description = "Base URL for the deployed API"
  value       = module.serverless.api_gateway_invoke_url
}

output "list_items_arn" {
  value = module.serverless.function_arns["ListItems"]
}

output "create_item_arn" {
  value = module.serverless.function_arns["CreateItem"]
}

output "table_name" {
  value = module.serverless.custom_dynamodb_table_names["ItemsTable"]
}

Deploy

terraform init
terraform plan
terraform apply

# Output example:
# api_url = "https://abc123.execute-api.us-east-1.amazonaws.com/prod"

SAM-specific behaviour

  • Globals.Function defaults are merged into every function
  • !Ref, !Sub, !GetAtt, and other intrinsics resolve at plan time
  • Parameters use their Default unless overridden by sam_template_parameters
  • Function keys are the SAM logical IDs (ListItems, CreateItem)
  • Policies on a function become statements on its generated IAM role

Next steps