AWS SAM & CloudFormation
A comprehensive deep dive into AWS SAM and CloudFormation — SAM resource types, CLI workflows, local testing, CloudFormation intrinsic functions, change sets, stack policies, StackSets, drift detection, and IaC patterns for the DVA-C02 exam.
Mental Model
Both SAM and CloudFormation are Infrastructure-as-Code tools — you declare the desired state, AWS figures out how to make it real.
Core mental model: CloudFormation is the general-purpose IaC engine that manages any AWS resource as a stack. SAM is a shorthand layer on top of CloudFormation specifically for serverless — one SAM function declaration expands into a Lambda function, IAM execution role, log group, and optional event source mappings automatically.
Part 1 — AWS SAM
SAM Transform & Resource Types
Every SAM template must declare the transform at the top. The CloudFormation macro expands SAM shorthand into full CloudFormation resources at deploy time.
1AWSTemplateFormatVersion: '2010-09-09'
2Transform: AWS::Serverless-2016-10-31
3Description: My serverless application| SAM Resource | Expands into |
|---|---|
AWS::Serverless::Function | Lambda function + IAM execution role + CloudWatch log group + optional event source mappings |
AWS::Serverless::Api | API Gateway REST API + deployment + stage |
AWS::Serverless::HttpApi | API Gateway HTTP API (v2) + stage |
AWS::Serverless::SimpleTable | DynamoDB table with a single hash key |
AWS::Serverless::LayerVersion | Lambda layer version |
AWS::Serverless::Application | Nested SAR (Serverless Application Repository) application |
AWS::Serverless::StateMachine | Step Functions state machine + IAM role |
AWS::Serverless::Connector | Auto-generates IAM policies for resource-to-resource access |
Full SAM Template Example
1AWSTemplateFormatVersion: '2010-09-09'
2Transform: AWS::Serverless-2016-10-31
3
4Globals:
5 Function:
6 Runtime: nodejs20.x
7 MemorySize: 512
8 Timeout: 10
9 Tracing: Active # X-Ray tracing on all functions
10 Environment:
11 Variables:
12 NODE_ENV: !Ref Stage
13 Layers:
14 - !Ref SharedUtilsLayer
15
16Parameters:
17 Stage:
18 Type: String
19 Default: dev
20 AllowedValues: [dev, staging, prod]
21
22Resources:
23
24 # ── Lambda Function ──────────────────────────────────────────────────────────
25 GetOrdersFunction:
26 Type: AWS::Serverless::Function
27 Properties:
28 CodeUri: src/handlers/getOrders/
29 Handler: index.handler
30 Description: Returns paginated order list for a user
31 ReservedConcurrentExecutions: 50
32 Policies:
33 - DynamoDBReadPolicy:
34 TableName: !Ref OrdersTable
35 - SSMParameterReadPolicy:
36 ParameterName: /myapp/prod/db-url
37 Events:
38 GetOrders:
39 Type: HttpApi
40 Properties:
41 ApiId: !Ref HttpApi
42 Path: /orders
43 Method: GET
44 Auth:
45 Authorizer: CognitoAuthorizer
46
47 # ── HTTP API (v2) ─────────────────────────────────────────────────────────────
48 HttpApi:
49 Type: AWS::Serverless::HttpApi
50 Properties:
51 StageName: !Ref Stage
52 Auth:
53 Authorizers:
54 CognitoAuthorizer:
55 IdentitySource: $request.header.Authorization
56 JwtConfiguration:
57 issuer: !Sub https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}
58 audience:
59 - !Ref UserPoolClient
60 DefaultAuthorizer: CognitoAuthorizer
61
62 # ── DynamoDB Table ────────────────────────────────────────────────────────────
63 OrdersTable:
64 Type: AWS::Serverless::SimpleTable
65 Properties:
66 PrimaryKey:
67 Name: orderId
68 Type: String
69 ProvisionedThroughput:
70 ReadCapacityUnits: 5
71 WriteCapacityUnits: 5
72 SSESpecification:
73 SSEEnabled: true
74
75 # ── Lambda Layer ──────────────────────────────────────────────────────────────
76 SharedUtilsLayer:
77 Type: AWS::Serverless::LayerVersion
78 Properties:
79 LayerName: shared-utils
80 ContentUri: src/layers/shared/
81 CompatibleRuntimes:
82 - nodejs20.x
83 RetentionPolicy: Retain
84
85Outputs:
86 ApiUrl:
87 Description: HTTP API endpoint URL
88 Value: !Sub https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}
89 Export:
90 Name: !Sub ${AWS::StackName}-ApiUrlSAM Policy Templates (Shorthand IAM)
SAM provides ~50 pre-built policy templates so you don't write raw IAM:
| Policy Template | Grants |
|---|---|
DynamoDBReadPolicy | GetItem, Query, Scan, BatchGetItem |
DynamoDBCrudPolicy | Read + PutItem, UpdateItem, DeleteItem |
DynamoDBStreamReadPolicy | Read DynamoDB stream |
S3ReadPolicy | GetObject, ListBucket |
S3CrudPolicy | Full S3 CRUD |
SQSPollerPolicy | ReceiveMessage, DeleteMessage, GetQueueAttributes |
SQSSendMessagePolicy | SendMessage |
SNSPublishMessagePolicy | sns:Publish |
SSMParameterReadPolicy | GetParameter, GetParameters |
SecretsManagerRotationPolicy | Rotation Lambda permissions |
StepFunctionsExecutionPolicy | StartExecution |
VPCAccessPolicy | CreateNetworkInterface, DeleteNetworkInterface |
KinesisStreamReadPolicy | GetRecords, GetShardIterator, DescribeStream |
SAM CLI Commands
| Command | What it does |
|---|---|
sam init | Scaffold a new SAM project from a template |
sam validate | Lint and validate the SAM template |
sam build | Compile code, install dependencies, output to .aws-sam/build/ |
sam local invoke | Run a single Lambda invocation locally (Docker) |
sam local start-api | Start a local HTTP server emulating API Gateway |
sam local start-lambda | Expose Lambda invoke endpoint for SDK testing |
sam local generate-event | Generate a sample event payload (S3, SQS, API GW…) |
sam deploy --guided | Interactive deploy with parameter prompts |
sam deploy | Deploy using samconfig.toml saved values |
sam sync | Hot-sync code changes to AWS without full deploy (dev only) |
sam logs -n FunctionName | Tail CloudWatch logs for a function |
sam traces | View X-Ray traces |
sam delete | Delete the stack |
Local Testing Workflow
1# Generate a test event payload
2sam local generate-event apigateway http-api-proxy > events/get-orders.json
3
4# Invoke a single function locally with the event
5sam local invoke GetOrdersFunction --event events/get-orders.json --env-vars env.json # override environment variables locally
6
7# Start a local API server (hot-reloads on code change)
8sam local start-api --port 3001
9
10# Pass environment variable overrides
11cat env.json
12# {
13# "GetOrdersFunction": {
14# "NODE_ENV": "local",
15# "TABLE_NAME": "orders-dev"
16# }
17# }samconfig.toml — Persist Deploy Settings
1version = 0.1
2
3[default.build.parameters]
4cached = true
5parallel = true
6
7[default.deploy.parameters]
8stack_name = "my-app-dev"
9s3_bucket = "my-deploy-bucket"
10s3_prefix = "my-app"
11region = "us-east-1"
12confirm_changeset = true
13capabilities = "CAPABILITY_IAM CAPABILITY_AUTO_EXPAND"
14parameter_overrides = "Stage=dev"
15
16[prod.deploy.parameters]
17stack_name = "my-app-prod"
18parameter_overrides = "Stage=prod"
19confirm_changeset = true1# Deploy to prod profile
2sam deploy --config-env prodSAM Connectors (Auto IAM)
Connectors automatically generate the least-privilege IAM policy between two resources:
1MyConnector:
2 Type: AWS::Serverless::Connector
3 Properties:
4 Source:
5 Id: GetOrdersFunction
6 Destination:
7 Id: OrdersTable
8 Permissions:
9 - Read
10 - WriteSAM resolves the resource types and generates the correct IAM actions — no hand-crafted policies needed.
Part 2 — AWS CloudFormation
Template Structure
1AWSTemplateFormatVersion: '2010-09-09' # always this value
2Description: Optional stack description
3
4Metadata: {} # arbitrary key-value metadata
5
6Parameters: {} # inputs — override at deploy time
7
8Mappings: {} # static lookup tables (region → AMI, etc.)
9
10Conditions: {} # boolean expressions based on parameters
11
12Transform: [] # macros (SAM, AWS::Include, etc.)
13
14Resources: {} # REQUIRED — actual AWS resources
15
16Outputs: {} # export values to other stacks or consoleIntrinsic Functions
| Function | Syntax | Use case |
|---|---|---|
Ref | !Ref LogicalId | Get the primary identifier of a resource or parameter value |
Fn::GetAtt | !GetAtt Resource.Attribute | Get a specific attribute (e.g., ARN, DNS name) |
Fn::Sub | !Sub 'Hello ${Name}' | String interpolation with resource/parameter values |
Fn::Join | !Join [',', [a, b, c]] | Concatenate values with a delimiter |
Fn::Select | !Select [0, !GetAZs ''] | Pick an item from a list by index |
Fn::Split | !Split [',', !Ref Csv] | Split a string into a list |
Fn::ImportValue | !ImportValue StackName-OutputKey | Import an exported output from another stack |
Fn::FindInMap | !FindInMap [Map, Key1, Key2] | Lookup a value in the Mappings section |
Fn::If | !If [Condition, IfTrue, IfFalse] | Conditional value based on a Condition |
Fn::Equals | !Equals [!Ref Env, prod] | Boolean equality check |
Fn::Not | !Not [!Equals [...]] | Boolean NOT |
Fn::And / Fn::Or | !And [...] | Boolean AND / OR |
Fn::Base64 | !Base64 !Sub '...' | Encode string as Base64 (EC2 UserData) |
Fn::Cidr | !Cidr [ipBlock, count, mask] | Generate CIDR block list |
Practical Intrinsic Function Examples
1Resources:
2 MyBucket:
3 Type: AWS::S3::Bucket
4 Properties:
5 BucketName: !Sub '${AWS::StackName}-data-${AWS::AccountId}'
6
7 MyFunction:
8 Type: AWS::Lambda::Function
9 Properties:
10 FunctionName: !Sub '${AWS::StackName}-processor'
11 # Reference another resource's attribute
12 Environment:
13 Variables:
14 BUCKET_ARN: !GetAtt MyBucket.Arn
15 BUCKET_NAME: !Ref MyBucket
16 REGION: !Ref AWS::Region
17 ACCOUNT: !Ref AWS::AccountId
18
19 MyQueuePolicy:
20 Type: AWS::SQS::QueuePolicy
21 Properties:
22 Queues:
23 - !Ref MyQueue
24 PolicyDocument:
25 Statement:
26 - Effect: Allow
27 Principal: '*'
28 Action: sqs:SendMessage
29 # Fn::Join to build complex strings
30 Resource: !Join ['', ['arn:aws:sqs:', !Ref AWS::Region, ':', !Ref AWS::AccountId, ':*']]
31
32Mappings:
33 RegionAMI:
34 us-east-1:
35 AMI: ami-0abcdef1234567890
36 eu-west-1:
37 AMI: ami-0fedcba9876543210
38
39# Usage: !FindInMap [RegionAMI, !Ref AWS::Region, AMI]
40
41Conditions:
42 IsProd: !Equals [!Ref Stage, prod]
43
44# Usage: !If [IsProd, t3.large, t3.micro]Pseudo Parameters
| Pseudo Parameter | Value |
|---|---|
AWS::AccountId | Current account ID |
AWS::Region | Deployment region |
AWS::StackName | Name of the current stack |
AWS::StackId | Full ARN of the current stack |
AWS::NoValue | Remove a property entirely (use in !If) |
AWS::URLSuffix | Domain suffix (amazonaws.com) |
AWS::Partition | aws, aws-cn, aws-us-gov |
Resource Attributes
1Resources:
2 MyDB:
3 Type: AWS::RDS::DBInstance
4 DependsOn: MySecurityGroup # explicit ordering
5 DeletionPolicy: Snapshot # Retain | Snapshot | Delete
6 UpdateReplacePolicy: Snapshot # what to do when resource is replaced
7 UpdatePolicy: # ASG rolling update behavior
8 AutoScalingRollingUpdate:
9 MinInstancesInService: 1
10 CreationPolicy: # wait for signal before marking CREATE_COMPLETE
11 ResourceSignal:
12 Count: 1
13 Timeout: PT10M
14 Metadata:
15 AWS::CloudFormation::Init: # cfn-init config sets
16 config:
17 packages:
18 yum:
19 httpd: []| DeletionPolicy | On stack delete |
|---|---|
Delete (default) | Resource is deleted |
Retain | Resource persists after stack deletion |
Snapshot | Snapshot created then resource deleted (RDS, EBS, ElastiCache) |
Change Sets
Change sets let you preview what CloudFormation will do before executing — critical for production stacks.
1# Create a change set
2aws cloudformation create-change-set --stack-name my-stack --template-body file://template.yaml --parameters ParameterKey=Stage,ParameterValue=prod --change-set-name my-changes --capabilities CAPABILITY_IAM
3
4# Review what will change
5aws cloudformation describe-change-set --stack-name my-stack --change-set-name my-changes
6
7# Execute (deploy the changes)
8aws cloudformation execute-change-set --stack-name my-stack --change-set-name my-changes
9
10# Delete (cancel without deploying)
11aws cloudformation delete-change-set --stack-name my-stack --change-set-name my-changesChange set action types:
| Action | Meaning |
|---|---|
Add | New resource will be created |
Modify | Existing resource will be updated |
Remove | Existing resource will be deleted |
Import | Existing resource being brought under stack management |
Replacement column: True means the resource will be deleted and recreated (causes downtime for stateful resources).
Stack Policies
Stack policies protect specific resources from accidental updates or deletes:
1{
2 "Statement": [
3 {
4 "Effect": "Deny",
5 "Action": "Update:Replace",
6 "Principal": "*",
7 "Resource": "LogicalResourceId/ProductionDatabase"
8 },
9 {
10 "Effect": "Allow",
11 "Action": "Update:*",
12 "Principal": "*",
13 "Resource": "*"
14 }
15 ]
16}1# Apply a stack policy
2aws cloudformation set-stack-policy --stack-name my-stack --stack-policy-body file://stack-policy.json
3
4# Override temporarily during an update (one-time bypass)
5aws cloudformation update-stack --stack-name my-stack --template-body file://template.yaml --stack-policy-during-update-body file://override-policy.jsonNested Stacks
Large templates are split into nested stacks for reuse and to stay under CloudFormation's resource limit per stack (500 resources).
1Resources:
2 VpcStack:
3 Type: AWS::CloudFormation::Stack
4 Properties:
5 TemplateURL: https://s3.amazonaws.com/my-bucket/vpc.yaml
6 Parameters:
7 VpcCidr: 10.0.0.0/16
8 TimeoutInMinutes: 10
9
10 AppStack:
11 Type: AWS::CloudFormation::Stack
12 DependsOn: VpcStack
13 Properties:
14 TemplateURL: https://s3.amazonaws.com/my-bucket/app.yaml
15 Parameters:
16 VpcId: !GetAtt VpcStack.Outputs.VpcId
17 SubnetIds: !GetAtt VpcStack.Outputs.PrivateSubnetIdsCross-Stack References
1# Stack A — export a value
2Outputs:
3 VpcId:
4 Value: !Ref MyVPC
5 Export:
6 Name: !Sub ${AWS::StackName}-VpcId # must be globally unique in the region
7
8# Stack B — import it
9Resources:
10 SecurityGroup:
11 Type: AWS::EC2::SecurityGroup
12 Properties:
13 VpcId: !ImportValue NetworkStack-VpcIdLimitation: You cannot delete Stack A while Stack B imports from it. Circular imports between stacks are not allowed.
StackSets — Multi-Account / Multi-Region Deployment
1# Create a StackSet (deploy to multiple accounts and regions)
2aws cloudformation create-stack-set --stack-set-name security-baseline --template-body file://security.yaml --capabilities CAPABILITY_NAMED_IAM --permission-model SERVICE_MANAGED # for AWS Organizations
3
4# Deploy instances to accounts in an OU
5aws cloudformation create-stack-instances --stack-set-name security-baseline --deployment-targets OrganizationalUnitIds=ou-xxxx-yyyyyyy --regions us-east-1 eu-west-1 --operation-preferences MaxConcurrentPercentage=25,FailureTolerancePercentage=10| Permission Model | How admin role is created |
|---|---|
SELF_MANAGED | You create AWSCloudFormationStackSetAdministrationRole manually |
SERVICE_MANAGED | AWS Organizations manages trusted access automatically |
Drift Detection
Drift occurs when a resource's actual configuration differs from its CloudFormation template (someone changed it manually via console or CLI).
1# Start drift detection on a stack
2aws cloudformation detect-stack-drift --stack-name my-stack
3
4# Check drift status
5aws cloudformation describe-stack-drift-detection-status --stack-drift-detection-id <id>
6
7# List drifted resources
8aws cloudformation describe-stack-resource-drifts --stack-name my-stack --stack-resource-drift-status-filters MODIFIED DELETED| Drift Status | Meaning |
|---|---|
IN_SYNC | Matches template |
MODIFIED | Actual config differs from expected |
DELETED | Resource no longer exists |
NOT_CHECKED | Resource type not supported for drift |
cfn-init & cfn-signal (EC2 Bootstrap)
1Resources:
2 WebServer:
3 Type: AWS::EC2::Instance
4 Metadata:
5 AWS::CloudFormation::Init:
6 config:
7 packages:
8 yum:
9 httpd: []
10 nodejs: []
11 files:
12 /var/www/html/index.html:
13 content: !Sub '<h1>Hello from ${AWS::StackName}</h1>'
14 mode: '000644'
15 services:
16 sysvinit:
17 httpd:
18 enabled: true
19 ensureRunning: true
20 CreationPolicy:
21 ResourceSignal:
22 Count: 1
23 Timeout: PT15M # wait up to 15 minutes for the signal
24 Properties:
25 UserData:
26 Fn::Base64: !Sub |
27 #!/bin/bash -xe
28 yum update -y aws-cfn-bootstrap
29 # Run cfn-init to apply Metadata::AWS::CloudFormation::Init
30 /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServer --region ${AWS::Region}
31 # Signal success/failure back to CloudFormation
32 /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServer --region ${AWS::Region}CloudFormation Capabilities
Some templates require explicit capabilities to avoid accidental privilege escalation:
| Capability | Required when |
|---|---|
CAPABILITY_IAM | Template creates IAM roles, policies, or instance profiles |
CAPABILITY_NAMED_IAM | Template creates IAM resources with custom names |
CAPABILITY_AUTO_EXPAND | Template uses macros (SAM transform, AWS::Include) |
SAM vs CloudFormation — When to Use Each
| SAM | CloudFormation | |
|---|---|---|
| Best for | Serverless (Lambda, API GW, DynamoDB, Step Functions) | Any AWS resource |
| Template verbosity | Low — shorthand resource types | High — full resource declarations |
| Local testing | ✅ sam local invoke | ❌ |
| Hot sync | ✅ sam sync | ❌ |
| Policy templates | ✅ 50+ shorthand policies | ❌ manual IAM |
| Connectors (auto IAM) | ✅ | ❌ |
| StackSets | ❌ (use CFN) | ✅ |
| Nested stacks | ✅ (via CFN) | ✅ |
| Drift detection | ❌ (use CFN) | ✅ |
| Change sets | ✅ (via CFN) | ✅ |
DVA-C02 Quick Reference
| Topic | Key Fact |
|---|---|
| SAM transform declaration | Transform: AWS::Serverless-2016-10-31 |
| SAM Function expands to | Lambda + IAM role + CloudWatch log group |
| SAM local invoke | Runs Lambda locally using Docker |
| SAM policy templates | Shorthand IAM — e.g., DynamoDBReadPolicy, S3CrudPolicy |
| SAM sync | Hot-deploys code changes without full CloudFormation stack update |
| samconfig.toml | Persists sam deploy settings so you don't need --guided every time |
| CloudFormation only required section | Resources |
!Ref on a resource | Returns primary identifier (e.g., bucket name, queue URL) |
!GetAtt | Returns a specific attribute (ARN, DNS name, etc.) |
!Sub | String interpolation — replaces ${LogicalId} with resolved value |
!ImportValue | Reads exported output from another stack |
!FindInMap | Looks up value in Mappings section |
| DeletionPolicy Retain | Resource persists after stack delete |
| DeletionPolicy Snapshot | Snapshot created before resource deleted (RDS, EBS) |
| Change set Replace=True | Resource will be deleted and recreated |
| Stack policy | Prevents specific resources from accidental update/delete |
| Stack policy override | --stack-policy-during-update-body (one-time bypass) |
| Cross-stack export delete | Cannot delete exporting stack while another stack imports from it |
| StackSets SERVICE_MANAGED | Uses AWS Organizations — no manual role creation |
| cfn-signal purpose | EC2 tells CloudFormation bootstrap is complete (CreationPolicy) |
| CAPABILITY_NAMED_IAM | Required when IAM resources have custom names |
| CAPABILITY_AUTO_EXPAND | Required when template uses macros (SAM, AWS::Include) |
| Drift detection finds | Resources manually changed outside CloudFormation |
Practice Questions3
Q1. A developer writes an AWS SAM template with an `AWS::Serverless::Function` resource. After running `sam deploy`, a reviewer inspects the CloudFormation stack and sees additional resources not in the SAM template. Why?
Select one answer before revealing.
Q2. A developer uses a CloudFormation template with hardcoded AMI IDs. The team deploys to multiple regions, but AMI IDs differ per region. How should the developer handle region-specific values without duplicating templates?
Select one answer before revealing.
Q3. A developer needs to share an S3 bucket ARN created in a "common-infrastructure" CloudFormation stack with a "web-app" stack. What is the correct CloudFormation mechanism?
Select one answer before revealing.