engineering-handbook/sam-project-layout.md
Adam Moussa 85de977098
docs(cd): separate terraform infra from github app deploys
Make HCP Terraform plus GitHub Actions content CD the default for new
workloads, and keep SAM/CDK documented as the remaining path.
2026-09-15 17:58:09 -04:00

3.3 KiB

SAM Project Layout

Remaining path for stacks that already use SAM. New deployables use terraform-project-layout.md and hcp-terraform.md.

Standard Directory Structure

project-name/
├── template.yaml            # SAM template at root
├── samconfig.toml           # Deploy config (gitignored)
├── samconfig.toml.example   # Template for onboarding (committed)
├── src/
│   ├── function_name/
│   │   ├── app.py           # Lambda handler
│   │   └── requirements.txt # Per-function dependencies
│   └── shared/              # Shared layer code (if needed)
└── .gitignore

File Purposes

template.yaml

The SAM/CloudFormation template. Lives at the project root. Defines all Lambda functions, IAM roles, API Gateway endpoints, DynamoDB tables, and other resources.

samconfig.toml

Contains real ARNs, S3 bucket names, and deploy parameters. Gitignored because it varies per environment and may contain account-specific values.

samconfig.toml.example

A committed template showing the expected structure and parameter names. New contributors copy this to samconfig.toml and fill in their values.

src/function_name/

One directory per Lambda function. Each contains its own handler (app.py) and dependencies (requirements.txt). This keeps functions independently deployable and avoids bloating one function with another's dependencies.

src/shared/

Optional. Used for code shared across multiple functions, typically deployed as a Lambda layer. See the next section for layout details.

Lambda Layers

When sharing code across functions via a layer, the source layout matters because SAM transforms ContentUri depending on whether BuildMethod is set.

Correct layout (with BuildMethod)

src/shared/
├── shared/             # Package goes here directly — SAM wraps in python/ at build time
│   ├── __init__.py
│   └── utils.py
└── requirements.txt    # Layer-level pip dependencies

Template:

SharedLayer:
  Type: AWS::Serverless::LayerVersion
  Properties:
    LayerName: my-stack-shared
    ContentUri: src/shared/
    CompatibleRuntimes:
      - python3.12
    CompatibleArchitectures:
      - arm64
  Metadata:
    BuildMethod: python3.12
    BuildArchitecture: arm64

BuildMethod nesting gotcha

With BuildMethod: python3.12, SAM copies ContentUri into a python/ subdirectory during build, then pip-installs requirements.txt deps into that same python/ directory.

Do not include a python/ wrapper in your source — SAM adds it. The wrong layout:

src/shared/
├── python/             # SAM wraps this again → python/python/shared/ — module unreachable
│   └── shared/
└── requirements.txt

A real production incident (~22 hours of outage) traced to this exact pattern when a refactor moved layer code under an extra python/ directory.

Without BuildMethod (raw zip)

If the layer has no pip dependencies and you omit BuildMethod, SAM zips ContentUri as-is — you DO need the python/ wrapper. Reserve raw zip for layers that ship only Python source.

Standard .gitignore

.aws-sam/
__pycache__/
*.pyc
.env
samconfig.toml