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 maarifaarchitect@gmail.com.


References​