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 Questions6
Q1. A developer must create identical copies of an application's infrastructure — a VPC, Lambda functions, and DynamoDB tables — in development, staging, and production, in a repeatable and version-controlled way. Which approach is recommended?
Select one answer before revealing.
Q2. What is AWS SAM (Serverless Application Model)?
Select one answer before revealing.
Q3. In which two formats can AWS CloudFormation templates be written?
Select one answer before revealing.
Q4. 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.
Q5. 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.
Q6. 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.