Skip to main content

Infrastructure as Code (SAM/CloudFormation)

Why Infrastructure as Code?

Manual setup in AWS Console doesn't scale:

  • Hard to reproduce
  • Difficult to version
  • Error-prone
  • Can't automate deployment

Simple Explanation

What it is

Infrastructure as Code means you define cloud resources in files so they can be created and updated the same way every time.

Why we need it

Clicking around the console does not scale for teams. IaC makes environments repeatable and reviewable.

Benefits

  • Repeatable setups across dev, staging, and prod.
  • Version control for infrastructure changes.
  • Faster deployments with fewer manual mistakes.

Tradeoffs

  • Learning curve for templates and tooling.
  • More upfront structure than ad-hoc console changes.

Real-world examples (architecture only)

  • Template defines API + Function + Database in one stack.
  • Same template deploys dev and prod with different parameters.

Solution: Define infrastructure in code

# template.yaml
Resources:
MyLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: HelloWorld
Runtime: python3.12
Handler: app.handler
Code:
S3Bucket: my-lambda-code
S3Key: function.zip

This YAML file replaces 10 manual console clicks.

CloudFormation Basics

CloudFormation is AWS's Infrastructure as Code service.

  • Describe resources in JSON/YAML
  • CloudFormation creates, updates, deletes resources
  • Version control your infrastructure
  • Reproducible deployments

SAM: Serverless Application Model

SAM is a simplified subset of CloudFormation for serverless apps.

Example SAM Template

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
Function:
Timeout: 20
Runtime: python3.12

Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handler.handler
CodeUri: src/
Events:
HelloWorldApi:
Type: Api
Properties:
Path: /hello
Method: get

ItemsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: Items
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: itemId
AttributeType: S
KeySchema:
- AttributeName: itemId
KeyType: HASH

Outputs:
HelloWorldFunctionArn:
Value: !GetAtt HelloWorldFunction.Arn
ApiEndpoint:
Value: !Sub 'https://${ServerlessApi}.execute-api.${AWS::Region}.amazonaws.com/Prod'

Deploying with SAM

Step 1: Install SAM CLI

brew install aws-sam-cli

Step 2: Initialize Project

sam init --runtime python3.12

Step 3: Build

sam build

Packages your code and dependencies.

Step 4: Deploy

sam deploy --guided

Prompts:

  • Stack name: maarifa-app
  • Region: us-east-1
  • Capabilities: Confirm

SAM creates all resources automatically.

Step 5: Update

sam deploy

Updates existing stack.

Key SAM Resources

Lambda Function

MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: app.handler
Runtime: python3.12
CodeUri: ./src
Environment:
Variables:
TABLE_NAME: MyTable
Policies:
- DynamoDBCrudPolicy:
TableName: MyTable

API Gateway

Events:
GetItem:
Type: Api
Properties:
Path: /items/{id}
Method: get
RestApiId: !Ref MyApi

DynamoDB Table

ItemsTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: itemId
AttributeType: S
- AttributeName: createdAt
AttributeType: S
KeySchema:
- AttributeName: itemId
KeyType: HASH
- AttributeName: createdAt
KeyType: RANGE
BillingMode: PAY_PER_REQUEST

S3 Bucket

MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-app-bucket-123

Environment Variables

Pass config to Lambda without hardcoding:

MyFunction:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
TABLE_NAME: !Ref ItemsTable
BUCKET_NAME: !Ref MyBucket
REGION: !Ref AWS::Region

Access in code:

import os

table_name = os.environ.get("TABLE_NAME")
bucket = os.environ.get("BUCKET_NAME")

Outputs

Export values from stack for reference:

Outputs:
ApiEndpoint:
Value: !GetAtt MyApi.Arn
TableName:
Value: !Ref ItemsTable
FunctionArn:
Value: !GetAtt MyFunction.Arn

Use outputs to:

  • Share with other stacks
  • Manual reference
  • Access in CI/CD pipelines

Parameters

Make templates reusable:

Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]

Resources:
ItemsTable:
Properties:
TableName: !Sub 'Items-${Environment}'

Deploy with parameter:

sam deploy --parameter-overrides Environment=prod

Best Practices

  1. Version control templates — Use git
  2. Use parameters — Don't hardcode names
  3. Organize stacks — One stack per app or component
  4. Use secrets — Keep sensitive data in AWS Secrets Manager
  5. Test templates — Validate before deploying

Hands-On: Deploy a Serverless TODO API

Create template.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
TodoFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handler.handler
Runtime: python3.12
CodeUri: ./
Events:
CreateTodo:
Type: Api
Properties:
Path: /todos
Method: post
GetTodo:
Type: Api
Properties:
Path: /todos/{id}
Method: get

TodoTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: todos
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH

Then:

sam build
sam deploy --guided

All resources created automatically!

Key Takeaway

Infrastructure as Code brings reliability and scalability to AWS deployments. SAM makes it simple for serverless apps.


Project (Cloud-Agnostic)

Define a minimal template that deploys a function and a database table.

Deliverables:

  1. Describe the resources and their relationships.
  2. Map the template to AWS (SAM) or GCP (Terraform).
  3. Explain how parameters make it reusable.

If you want feedback, email your write-up to [email protected].


References