engineering-handbook/sam-project-layout.md
Adam Moussa 651dff5dd3
Add Bedrock, dev-env, and Lambda template pages (#7)
Adds three handbook pages covering conventions that were previously
scattered across feedback memories or rederived from scratch each
time:

- bedrock.md captures the cross-region inference profile requirement
  for Claude 4.x Bedrock Agents and the alias-version pinning gotcha,
  plus the IAM resource pattern and the KB Docker requirement.
- dev-environment.md documents the workstation directory layout,
  pyenv/Node conventions, the macOS launchd/TCC sandbox gotcha, and
  cleanup cadence.
- lambda-template.md provides a minimal SAM scaffold that follows the
  Lambda defaults already in aws-infrastructure.md (Python 3.12,
  arm64, explicit 60-day log retention, scoped Secrets Manager
  access, module-level secret cache).

Also extends two existing pages:

- sam-project-layout.md gains a Lambda Layers section with the
  BuildMethod nesting pattern that caused a ~22-hour production
  outage when violated.
- naming-conventions.md adds a Legacy Stacks note acknowledging that
  pre-convention PascalCase stacks (SeaHavenDoorUnlockStack,
  WorkorderIngestStack) stay as-is rather than risk stack
  replacement.
2026-05-14 19:50:37 -04:00

3.1 KiB

SAM Project Layout

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