Infrastructure as Code: CloudFormation, CDK & Terraform
Clicking through the console to build a VPC, a bucket, a table and three functions takes an afternoon. Doing it again for a test environment takes another afternoon, and the two never end up exactly the same. Infrastructure as code (IaC) means describing your resources in files, keeping those files in Git, and letting a tool create, update and delete the real resources to match.
In this lesson you will learn why IaC matters, write and deploy a CloudFormation template, see the same idea in AWS SAM, the AWS CDK and Terraform, and learn how to choose between them.
Why infrastructure as code?
- Repeatable: the same file creates identical dev, test and production environments, in Mumbai or Hyderabad.
- Reviewable: changes go through pull requests, just like application code, and Git history shows who changed what.
- Previewable: you see what will change before it changes.
- Clean up: deleting a stack removes everything it created, so no forgotten resources keep billing.
- Documentation: the code is the up-to-date description of your system.
AWS CloudFormation
CloudFormation is AWS's built-in IaC service. You write a template in YAML or JSON, and CloudFormation creates a stack: the set of real resources described by the template. Its main sections are:
AWSTemplateFormatVersion: always"2010-09-09"(like the IAM version, a fixed value).Parameters: inputs, such as an environment name.Resources: the only required section; each resource has a logical name, aTypeandProperties.Outputs: values to show or export, such as a bucket name.
Intrinsic functions connect things: !Ref returns a parameter's value or a resource's main ID, !GetAtt returns an attribute (such as an ARN), and !Sub builds strings with ${...} placeholders.
AWSTemplateFormatVersion: "2010-09-09"
Description: Storage for the tiffin app
Parameters:
Env:
Type: String
AllowedValues: [dev, prod]
Default: dev
Resources:
ReceiptsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "tiffin-receipts-${Env}-${AWS::AccountId}"
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
IgnorePublicAcls: true
BlockPublicPolicy: true
RestrictPublicBuckets: true
OrdersTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: customerId
AttributeType: S
- AttributeName: orderSk
AttributeType: S
KeySchema:
- AttributeName: customerId
KeyType: HASH
- AttributeName: orderSk
KeyType: RANGE
Outputs:
BucketName:
Value: !Ref ReceiptsBucket
TableArn:
Value: !GetAtt OrdersTable.Arn${AWS::AccountId} is a pseudo parameter that CloudFormation fills in, which helps make the bucket name unique. Deploy the template with one command. It creates a change set (a preview of changes) and executes it:
aws cloudformation deploy --template-file storage.yaml \
--stack-name tiffin-storage-dev --parameter-overrides Env=dev
aws cloudformation describe-stacks --stack-name tiffin-storage-dev \
--query "Stacks[0].Outputs[].[OutputKey, OutputValue]" --output textWaiting for changeset to be created.. Waiting for stack create/update to complete Successfully created/updated stack - tiffin-storage-dev BucketName tiffin-receipts-dev-111122223333 TableArn arn:aws:dynamodb:ap-south-1:111122223333:table/tiffin-storage-dev-OrdersTable-1A2B3C4D5E6F
Because the table has no fixed name, CloudFormation generated one from the stack and logical names. Run deploy again after editing the file and only the differences are applied. For templates that create IAM resources, add --capabilities CAPABILITY_IAM to confirm you allow that. aws cloudformation delete-stack --stack-name tiffin-storage-dev removes everything (S3 buckets must be empty first).
Other useful features: drift detection finds resources changed by hand outside CloudFormation, stack policies and termination protection guard production stacks, and the IaC generator can create a template from resources you already built in the console.
AWS SAM for serverless apps
The AWS Serverless Application Model (SAM) is an extension of CloudFormation with short resource types for serverless apps. One AWS::Serverless::Function with an event creates the function, its role, its log permissions and an HTTP API:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
BillFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.14
Architectures: [arm64]
Handler: app.lambda_handler
CodeUri: src/
Timeout: 10
Events:
Bill:
Type: HttpApi
Properties:
Path: /bill
Method: postsam build packages the code and sam deploy --guided asks a few questions the first time and deploys. sam local invoke runs the function in a local container for testing.
AWS CDK: infrastructure in a programming language
The AWS Cloud Development Kit (CDK) lets you write infrastructure in TypeScript, JavaScript, Python, Java, C# or Go. Your code synthesises a CloudFormation template, which is then deployed. Its libraries provide constructs with safe defaults, so a few lines produce many correctly configured resources. Use CDK v2 (the aws-cdk-lib package); v1 reached end of support in June 2023.
from aws_cdk import App, Environment, RemovalPolicy, Stack
from aws_cdk import aws_dynamodb as dynamodb
from aws_cdk import aws_s3 as s3
from constructs import Construct
class TiffinStorage(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
s3.Bucket(self, "Receipts",
versioned=True,
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
enforce_ssl=True,
removal_policy=RemovalPolicy.RETAIN)
dynamodb.TableV2(self, "Orders",
partition_key=dynamodb.Attribute(name="customerId", type=dynamodb.AttributeType.STRING),
sort_key=dynamodb.Attribute(name="orderSk", type=dynamodb.AttributeType.STRING),
billing=dynamodb.Billing.on_demand())
app = App()
TiffinStorage(app, "TiffinStorage", env=Environment(region="ap-south-1"))
app.synth()npm install -g aws-cdk
cdk init app --language python
cdk bootstrap
cdk synth
cdk diff
cdk deploy
cdk destroycdk bootstrap runs once per account and Region to create the resources CDK needs for deployments. cdk synth prints the generated template, cdk diff previews changes, and enforce_ssl=True adds the "deny non-HTTPS" bucket policy you wrote by hand in the S3 lesson.
Terraform and OpenTofu
Terraform, from HashiCorp (now part of IBM), is a popular IaC tool that works with AWS and hundreds of other providers using its own language, HCL. It keeps a state file that maps your code to real resources; for teams, store it in an S3 bucket with locking. Terraform changed to the Business Source Licence in 2023, and OpenTofu is an open-source fork managed by the Linux Foundation that uses the same language.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
backend "s3" {
bucket = "priya-tf-state-111122223333"
key = "tiffin/terraform.tfstate"
region = "ap-south-1"
use_lockfile = true
}
}
provider "aws" {
region = "ap-south-1"
}
resource "aws_s3_bucket" "receipts" {
bucket = "tiffin-receipts-tf-111122223333"
}
resource "aws_s3_bucket_versioning" "receipts" {
bucket = aws_s3_bucket.receipts.id
versioning_configuration {
status = "Enabled"
}
}The workflow is terraform init, then terraform plan (preview), terraform apply and, when finished, terraform destroy. use_lockfile = true uses S3's own locking; older setups used a DynamoDB table for locks.
Which tool should you choose?
| Tool | Language | Best when |
|---|---|---|
| CloudFormation | YAML or JSON | You want AWS-native, no extra tools, and simple templates |
| AWS SAM | YAML (CloudFormation extension) | You build mainly Lambda and API Gateway apps |
| AWS CDK | TypeScript, Python, Java, C#, Go | Developers prefer real code, loops and reusable components |
| Terraform or OpenTofu | HCL | You manage several clouds or the team already uses it |
All of them are far better than clicking. Pick one per project and do not mix manual console changes into resources it manages.
Common mistakes
- Changing IaC-managed resources in the console, causing drift and surprises at the next deploy.
- Hard-coding account IDs, Regions and names instead of parameters and pseudo parameters.
- Committing secrets into templates or Terraform variables files.
- Keeping Terraform state on one laptop. Use a remote backend with locking.
- Deploying without a preview. Always look at the change set,
cdk difforterraform planfirst.
Key points
- IaC makes infrastructure repeatable, reviewable, previewable and easy to delete.
- CloudFormation templates have
Parameters,ResourcesandOutputs;!Ref,!GetAttand!Subconnect values; stacks group resources. aws cloudformation deploycreates and executes a change set; deleting the stack removes its resources.- AWS SAM shortens serverless templates; the AWS CDK v2 generates CloudFormation from TypeScript, Python and other languages.
- Terraform and OpenTofu use HCL and a state file, best stored in S3 with locking.
Exercise
Write a CloudFormation template with a parameter ClubName and one S3 bucket whose name is the club name, then -match-photos-, then the account ID, with versioning enabled and an output called PhotosBucket. Then write the command to deploy it as stack cricket-photos with ClubName=rohtak-xi.
Show solution
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
ClubName:
Type: String
Resources:
PhotosBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${ClubName}-match-photos-${AWS::AccountId}"
VersioningConfiguration:
Status: Enabled
Outputs:
PhotosBucket:
Value: !Ref PhotosBucketaws cloudformation deploy --template-file photos.yaml \
--stack-name cricket-photos --parameter-overrides ClubName=rohtak-xi!Sub replaces both the parameter and the pseudo parameter, and !Ref on a bucket returns its name. Bucket names must be lowercase, which is why the club name uses lowercase letters and a hyphen.
Quick Quiz
1. Which CloudFormation template section is required?
A template must have at least one resource; other sections are optional.
2. What does !GetAtt OrdersTable.Arn return?
!GetAtt returns an attribute of a resource; !Ref returns its main ID or name.
3. What does cdk synth do?
CDK code synthesises into a CloudFormation template that is then deployed.
4. Where should a team keep Terraform state?
Remote state with locking prevents two people applying changes at once and keeps state safe.
Learn AWS with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
