Docs
/
AWS Cloud
Chapter 16

16 — Infrastructure as Code

Why IaC?

Manual (Console):                    IaC:
Click buttons → create resources     Write code → deploy resources
Not reproducible                     Version controlled, repeatable
Drift over time                      Drift detection
No audit trail                       Git history = audit trail

AWS CloudFormation

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: My application stack

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

Resources:
  AppBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub my-app-${Environment}-assets
      VersioningConfiguration:
        Status: Enabled

  ApiFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub my-app-${Environment}-api
      Runtime: nodejs20.x
      Handler: index.handler
      Code:
        S3Bucket: my-deploy-bucket
        S3Key: api.zip
      Environment:
        Variables:
          TABLE_NAME: !Ref OrdersTable

  OrdersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub orders-${Environment}
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: pk
          AttributeType: S
        - AttributeName: sk
          AttributeType: S
      KeySchema:
        - AttributeName: pk
          KeyType: HASH
        - AttributeName: sk
          KeyType: RANGE

Outputs:
  ApiArn:
    Value: !GetAtt ApiFunction.Arn
  TableName:
    Value: !Ref OrdersTable
aws cloudformation deploy \
  --template-file template.yaml \
  --stack-name my-app-dev \
  --parameter-overrides Environment=dev \
  --capabilities CAPABILITY_IAM

AWS CDK (Cloud Development Kit)

Define infrastructure in TypeScript (or Python, Java, Go, C#).

import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';

export class MyAppStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // DynamoDB table
    const table = new dynamodb.Table(this, 'Orders', {
      partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
      sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });

    // Lambda function
    const api = new lambda.Function(this, 'ApiHandler', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'dist/handler.handler',
      code: lambda.Code.fromAsset('lambda'),
      environment: { TABLE_NAME: table.tableName },
      memorySize: 256,
      timeout: cdk.Duration.seconds(30),
    });

    // Grant Lambda read/write access to DynamoDB
    table.grantReadWriteData(api);

    // API Gateway
    new apigateway.LambdaRestApi(this, 'Endpoint', {
      handler: api,
      proxy: true,
    });

    // S3 bucket
    new s3.Bucket(this, 'Assets', {
      versioned: true,
      encryption: s3.BucketEncryption.S3_MANAGED,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });
  }
}
npm install -g aws-cdk
cdk init app --language typescript
cdk synth          # Generate CloudFormation template
cdk diff           # Preview changes
cdk deploy         # Deploy
cdk destroy        # Tear down

Terraform Basics

Provider-agnostic IaC (works with AWS, GCP, Azure, etc.).

# main.tf
provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "assets" {
  bucket = "my-app-assets"
}

resource "aws_dynamodb_table" "orders" {
  name         = "orders"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "pk"
  range_key    = "sk"

  attribute {
    name = "pk"
    type = "S"
  }
  attribute {
    name = "sk"
    type = "S"
  }
}

resource "aws_lambda_function" "api" {
  function_name = "my-api"
  runtime       = "nodejs20.x"
  handler       = "dist/handler.handler"
  filename      = "lambda.zip"
  role          = aws_iam_role.lambda_role.arn

  environment {
    variables = {
      TABLE_NAME = aws_dynamodb_table.orders.name
    }
  }
}
terraform init       # Install providers
terraform plan       # Preview changes
terraform apply      # Deploy
terraform destroy    # Tear down

Comparison

CloudFormationCDKTerraform
LanguageYAML/JSONTypeScript, Python, etc.HCL
ProviderAWS onlyAWS onlyMulti-cloud
StateAWS-managedAWS-managedFile/remote backend
Learning curveMediumLow (if you know TS)Medium
Best forAWS-native teamsDevelopers who prefer codeMulti-cloud, existing Terraform teams

Key Takeaways

  • IaC = reproducible, version-controlled, auditable infrastructure
  • CDK (TypeScript) is the best choice for developers — real code with IDE support
  • CloudFormation is the foundation (CDK generates it)
  • Terraform for multi-cloud or if team already uses it
  • Always use cdk diff / terraform plan before deploying
  • Store state remotely (S3 + DynamoDB for Terraform lock)