Skip to main content
/ Cloud

Infrastructure as Code: A Beginner's Guide to Terraform and CloudFormation

Sacha Roussakis-NotterSacha Roussakis-Notter
14 min read
Terraform
AWS
Share

Learn infrastructure as code fundamentals with practical examples. Covers Terraform, CloudFormation, and best practices for reproducible deployments.

Why Infrastructure as Code Matters

In 2026, manually clicking through cloud consoles isn't just inefficient—it's a risk. Infrastructure as Code (IaC) replaces fragile manual processes with scripts and configuration files that can be version-controlled, peer-reviewed, and reused.

The results speak for themselves:

  • 65% faster provisioning for new environments
  • 70% reduction in infrastructure drift between staging and production
  • Compliance audit preparation shortened to under 5 business days

The IaC market is growing at a 24% CAGR, and organisations adopting IaC report dramatically improved reliability, faster deployments, and reduced operational costs.

This guide covers the fundamentals you need to get started with Terraform and CloudFormation—the two most widely used IaC tools.

What Is Infrastructure as Code?

Infrastructure as Code treats your cloud resources—servers, networks, databases, load balancers—as software that can be defined, versioned, and deployed programmatically.

flowchart

IaC Approach

Write Code

Review & Test

Version Control

Deploy Consistently

Manual Approach

Login to Console

Click Through Wizards

Configure Settings

Hope You Remember

Ctrl+scroll to zoom • Drag to pan46%

Key Benefits

BenefitImpact
ConsistencyIdentical environments every time
Version controlTrack changes, rollback if needed
AutomationDeploy with a single command
DocumentationCode describes your infrastructure
CollaborationCode review for infrastructure changes
Disaster recoveryRebuild environments in minutes

Core Concepts

ConceptDescription
DeclarativeYou describe what you want, not how to build it
IdempotentRunning the same code twice produces the same result
StateTracks what resources exist and their configuration
PlanPreview changes before applying them
Drift detectionIdentify when reality differs from code

Terraform vs CloudFormation: Choosing Your Tool

Overview Comparison

FactorTerraformCloudFormation
CreatorHashiCorp (now IBM)AWS
LanguageHCL (HashiCorp Configuration Language)YAML or JSON
Multi-cloudYes (AWS, Azure, GCP, etc.)AWS only
State managementSelf-managed or Terraform CloudAWS-managed
Drift detectionCommunity tools, limited nativeBuilt-in
Learning curveModerateModerate
CommunityVery largeLarge (AWS-focused)

When to Choose Terraform

flowchart

Choose Terraform When

Multi-Cloud Strategy

Non-AWS Resources

Team Prefers HCL

Existing Terraform Expertise

Need Provider Flexibility

Ctrl+scroll to zoom • Drag to pan40%

Terraform excels for:

  • Organisations using multiple cloud providers
  • Teams wanting cloud-agnostic skills
  • Projects requiring providers beyond AWS (Cloudflare, Datadog, GitHub, etc.)
  • Startups that may change cloud providers

When to Choose CloudFormation

flowchart

Choose CloudFormation When

100% AWS Environment

Prefer Managed State

Need Drift Detection

Regulated Environment

Already Using AWS CDK

Ctrl+scroll to zoom • Drag to pan39%

CloudFormation excels for:

  • Organisations fully committed to AWS
  • Regulated industries requiring AWS-native tooling
  • Teams preferring managed state without external dependencies
  • Projects using AWS CDK for TypeScript/Python infrastructure

The OpenTofu Factor

In 2023, HashiCorp changed Terraform's license to Business Source License (BSL), prompting the community to fork the project as OpenTofu—an open-source alternative under the Linux Foundation.

FactorTerraformOpenTofu
LicenseBSL (commercial restrictions)Open-source (MPL 2.0)
CompatibilityOriginalFork, compatible with Terraform
CommunityHashiCorp-ledLinux Foundation, community-led
Enterprise featuresTerraform Cloud/EnterpriseCommunity-driven alternatives

For new projects: Consider OpenTofu if open-source licensing is important to your organisation.

Getting Started with Terraform

Installation

bash
1# macOS (Homebrew)
2brew tap hashicorp/tap
3brew install hashicorp/tap/terraform
4
5# Ubuntu/Debian
6wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
7echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
8sudo apt update && sudo apt install terraform
9
10# Verify installation
11terraform version

Your First Terraform Configuration

Create a file named main.tf:

hcl
1# Configure the AWS Provider
2terraform {
3 required_providers {
4 aws = {
5 source = "hashicorp/aws"
6 version = "~> 5.0"
7 }
8 }
9}
10
11provider "aws" {
12 region = "ap-southeast-2" # Sydney
13}
14
15# Create a VPC
16resource "aws_vpc" "main" {
17 cidr_block = "10.0.0.0/16"
18 enable_dns_hostnames = true
19 enable_dns_support = true
20
21 tags = {
22 Name = "main-vpc"
23 Environment = "production"
24 ManagedBy = "terraform"
25 }
26}
27
28# Create a public subnet
29resource "aws_subnet" "public" {
30 vpc_id = aws_vpc.main.id
31 cidr_block = "10.0.1.0/24"
32 availability_zone = "ap-southeast-2a"
33 map_public_ip_on_launch = true
34
35 tags = {
36 Name = "public-subnet"
37 }
38}
39
40# Create an internet gateway
41resource "aws_internet_gateway" "main" {
42 vpc_id = aws_vpc.main.id
43
44 tags = {
45 Name = "main-igw"
46 }
47}

Terraform Workflow

bash
1# 1. Initialise (download providers)
2terraform init
3
4# 2. Format code
5terraform fmt
6
7# 3. Validate syntax
8terraform validate
9
10# 4. Preview changes
11terraform plan
12
13# 5. Apply changes
14terraform apply
15
16# 6. Destroy when done (be careful!)
17terraform destroy

Variables and Outputs

variables.tf:

hcl
1variable "environment" {
2 description = "Environment name"
3 type = string
4 default = "development"
5}
6
7variable "vpc_cidr" {
8 description = "CIDR block for VPC"
9 type = string
10 default = "10.0.0.0/16"
11}
12
13variable "instance_type" {
14 description = "EC2 instance type"
15 type = string
16 default = "t3.micro"
17
18 validation {
19 condition = can(regex("^t3\.", var.instance_type))
20 error_message = "Instance type must be t3 family."
21 }
22}

outputs.tf:

hcl
1output "vpc_id" {
2 description = "ID of the VPC"
3 value = aws_vpc.main.id
4}
5
6output "subnet_id" {
7 description = "ID of the public subnet"
8 value = aws_subnet.public.id
9}

Getting Started with CloudFormation

Your First CloudFormation Template

Create a file named vpc-stack.yaml:

yaml
1AWSTemplateFormatVersion: '2010-09-09'
2Description: 'Basic VPC with public subnet'
3
4Parameters:
5 Environment:
6 Type: String
7 Default: development
8 AllowedValues:
9 - development
10 - staging
11 - production
12
13 VpcCidr:
14 Type: String
15 Default: '10.0.0.0/16'
16 Description: CIDR block for the VPC
17
18Resources:
19 MainVPC:
20 Type: AWS::EC2::VPC
21 Properties:
22 CidrBlock: !Ref VpcCidr
23 EnableDnsHostnames: true
24 EnableDnsSupport: true
25 Tags:
26 - Key: Name
27 Value: !Sub '${Environment}-vpc'
28 - Key: Environment
29 Value: !Ref Environment
30
31 PublicSubnet:
32 Type: AWS::EC2::Subnet
33 Properties:
34 VpcId: !Ref MainVPC
35 CidrBlock: '10.0.1.0/24'
36 AvailabilityZone: !Select
37 - 0
38 - !GetAZs ''
39 MapPublicIpOnLaunch: true
40 Tags:
41 - Key: Name
42 Value: !Sub '${Environment}-public-subnet'
43
44 InternetGateway:
45 Type: AWS::EC2::InternetGateway
46 Properties:
47 Tags:
48 - Key: Name
49 Value: !Sub '${Environment}-igw'
50
51 AttachGateway:
52 Type: AWS::EC2::VPCGatewayAttachment
53 Properties:
54 VpcId: !Ref MainVPC
55 InternetGatewayId: !Ref InternetGateway
56
57Outputs:
58 VpcId:
59 Description: VPC ID
60 Value: !Ref MainVPC
61 Export:
62 Name: !Sub '${Environment}-VpcId'
63
64 SubnetId:
65 Description: Public Subnet ID
66 Value: !Ref PublicSubnet
67 Export:
68 Name: !Sub '${Environment}-PublicSubnetId'

CloudFormation Workflow

bash
1# Validate template
2aws cloudformation validate-template \
3 --template-body file://vpc-stack.yaml
4
5# Create stack
6aws cloudformation create-stack \
7 --stack-name my-vpc-stack \
8 --template-body file://vpc-stack.yaml \
9 --parameters ParameterKey=Environment,ParameterValue=development
10
11# Check stack status
12aws cloudformation describe-stacks \
13 --stack-name my-vpc-stack
14
15# Update stack
16aws cloudformation update-stack \
17 --stack-name my-vpc-stack \
18 --template-body file://vpc-stack.yaml
19
20# Delete stack
21aws cloudformation delete-stack \
22 --stack-name my-vpc-stack

Starter Templates

Terraform: Web Application Stack

hcl
1# main.tf - Complete web application infrastructure
2
3terraform {
4 required_version = ">= 1.0"
5 required_providers {
6 aws = {
7 source = "hashicorp/aws"
8 version = "~> 5.0"
9 }
10 }
11
12 # Remote state (recommended for teams)
13 backend "s3" {
14 bucket = "your-terraform-state-bucket"
15 key = "web-app/terraform.tfstate"
16 region = "ap-southeast-2"
17 encrypt = true
18 dynamodb_table = "terraform-locks"
19 }
20}
21
22provider "aws" {
23 region = var.aws_region
24
25 default_tags {
26 tags = {
27 Project = var.project_name
28 Environment = var.environment
29 ManagedBy = "terraform"
30 }
31 }
32}
33
34# VPC Module (use official AWS module)
35module "vpc" {
36 source = "terraform-aws-modules/vpc/aws"
37 version = "~> 5.0"
38
39 name = "${var.project_name}-vpc"
40 cidr = var.vpc_cidr
41
42 azs = ["${var.aws_region}a", "${var.aws_region}b"]
43 private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
44 public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
45
46 enable_nat_gateway = true
47 single_nat_gateway = var.environment != "production"
48}
49
50# Application Load Balancer
51resource "aws_lb" "main" {
52 name = "${var.project_name}-alb"
53 internal = false
54 load_balancer_type = "application"
55 security_groups = [aws_security_group.alb.id]
56 subnets = module.vpc.public_subnets
57}
58
59# Security Group for ALB
60resource "aws_security_group" "alb" {
61 name = "${var.project_name}-alb-sg"
62 description = "Security group for ALB"
63 vpc_id = module.vpc.vpc_id
64
65 ingress {
66 from_port = 443
67 to_port = 443
68 protocol = "tcp"
69 cidr_blocks = ["0.0.0.0/0"]
70 }
71
72 ingress {
73 from_port = 80
74 to_port = 80
75 protocol = "tcp"
76 cidr_blocks = ["0.0.0.0/0"]
77 }
78
79 egress {
80 from_port = 0
81 to_port = 0
82 protocol = "-1"
83 cidr_blocks = ["0.0.0.0/0"]
84 }
85}

CloudFormation: Web Application Stack

yaml
1AWSTemplateFormatVersion: '2010-09-09'
2Description: 'Web Application Infrastructure Stack'
3
4Parameters:
5 ProjectName:
6 Type: String
7 Description: Project name for resource naming
8 Environment:
9 Type: String
10 AllowedValues: [development, staging, production]
11 VpcCidr:
12 Type: String
13 Default: '10.0.0.0/16'
14
15Resources:
16 # VPC
17 VPC:
18 Type: AWS::EC2::VPC
19 Properties:
20 CidrBlock: !Ref VpcCidr
21 EnableDnsHostnames: true
22 EnableDnsSupport: true
23 Tags:
24 - Key: Name
25 Value: !Sub '${ProjectName}-${Environment}-vpc'
26
27 # Public Subnets
28 PublicSubnet1:
29 Type: AWS::EC2::Subnet
30 Properties:
31 VpcId: !Ref VPC
32 CidrBlock: '10.0.1.0/24'
33 AvailabilityZone: !Select [0, !GetAZs '']
34 MapPublicIpOnLaunch: true
35
36 PublicSubnet2:
37 Type: AWS::EC2::Subnet
38 Properties:
39 VpcId: !Ref VPC
40 CidrBlock: '10.0.2.0/24'
41 AvailabilityZone: !Select [1, !GetAZs '']
42 MapPublicIpOnLaunch: true
43
44 # Internet Gateway
45 InternetGateway:
46 Type: AWS::EC2::InternetGateway
47
48 AttachGateway:
49 Type: AWS::EC2::VPCGatewayAttachment
50 Properties:
51 VpcId: !Ref VPC
52 InternetGatewayId: !Ref InternetGateway
53
54 # Application Load Balancer
55 ApplicationLoadBalancer:
56 Type: AWS::ElasticLoadBalancingV2::LoadBalancer
57 Properties:
58 Name: !Sub '${ProjectName}-${Environment}-alb'
59 Subnets:
60 - !Ref PublicSubnet1
61 - !Ref PublicSubnet2
62 SecurityGroups:
63 - !Ref ALBSecurityGroup
64 Scheme: internet-facing
65 Type: application
66
67 # ALB Security Group
68 ALBSecurityGroup:
69 Type: AWS::EC2::SecurityGroup
70 Properties:
71 GroupDescription: Security group for ALB
72 VpcId: !Ref VPC
73 SecurityGroupIngress:
74 - IpProtocol: tcp
75 FromPort: 443
76 ToPort: 443
77 CidrIp: 0.0.0.0/0
78 - IpProtocol: tcp
79 FromPort: 80
80 ToPort: 80
81 CidrIp: 0.0.0.0/0
82
83Outputs:
84 VPCId:
85 Value: !Ref VPC
86 Export:
87 Name: !Sub '${ProjectName}-${Environment}-VPCId'
88 ALBDNSName:
89 Value: !GetAtt ApplicationLoadBalancer.DNSName
90 Export:
91 Name: !Sub '${ProjectName}-${Environment}-ALBDNSName'

Best Practices

Project Structure

plaintext
1infrastructure/
2 environments/
3 dev/
4 main.tf
5 variables.tf
6 terraform.tfvars
7 staging/
8 production/
9 modules/
10 vpc/
11 ecs/
12 rds/
13 shared/
14 backend.tf
15 README.md

Essential Practices

PracticeWhy It Matters
Use remote stateEnable team collaboration, prevent conflicts
Lock state filesPrevent concurrent modifications
Use modulesReusable, tested components
Environment separationIsolated configurations per environment
Review before applyAlways run plan first
Tag everythingTrack costs, ownership, lifecycle
Encrypt sensitive dataNever commit secrets to version control
CI/CD integrationAutomated, consistent deployments

Security Checklist

  • Store state in encrypted backend (S3 with encryption, Terraform Cloud)
  • Use IAM roles, not access keys
  • Never hardcode secrets—use AWS Secrets Manager or environment variables
  • Enable state file locking (DynamoDB for Terraform)
  • Implement least-privilege IAM policies
  • Review plan output for unintended changes
  • Use prevent_destroy for critical resources

Common Mistakes to Avoid

MistakeConsequencePrevention
Local stateTeam conflicts, lost stateUse remote backend from day one
Hardcoded valuesCan't reuse across environmentsUse variables for everything
No modulesCopy-paste, drift between environmentsExtract common patterns to modules
Skipping planUnexpected destructionsAlways review plan before apply
Manual changesDrift, broken stateNever modify resources outside IaC
No taggingCan't track costs or ownershipEnforce tags via policies

Getting Help

Learning Resources

ResourceTypeBest For
Terraform docsDocumentationReference, getting started
CloudFormation docsDocumentationAWS-specific patterns
HashiCorp LearnInteractive tutorialsHands-on practice
AWS Workshop StudioGuided workshopsCloudFormation deep dives

When to Bring in Experts

Consider professional help if:

  • You're setting up IaC for the first time across an organisation
  • You need to migrate existing manual infrastructure to code
  • Compliance requirements demand specific patterns
  • You want to establish best practices and train your team

About Buun Group

At Buun Group, we help organisations adopt Infrastructure as Code effectively. Our approach:

  • Pragmatic tool selection: We recommend what fits your needs, not what's trendy
  • Module libraries: Reusable, tested infrastructure components
  • CI/CD integration: Automated, safe deployments
  • Training and handover: Your team owns and understands the infrastructure

We've helped businesses move from manual deployments to fully automated infrastructure, reducing deployment time from days to minutes while improving reliability.

Ready to automate your infrastructure?

Topics

infrastructure as codeDevOps servicescloud consulting servicesTerraform tutorialCloudFormation guideIaC best practicesAWS infrastructureBrisbane DevOps

Share this post

Share

Comments

Sign in to join the conversation

Login

No comments yet. Be the first to share your thoughts!

Found an issue with this article?

/ Let's Talk

Want to work with us?

Whether you need help with architecture, development, or technical consulting, our team is here to help bring your vision to life.