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.
Key Benefits
| Benefit | Impact |
|---|---|
| Consistency | Identical environments every time |
| Version control | Track changes, rollback if needed |
| Automation | Deploy with a single command |
| Documentation | Code describes your infrastructure |
| Collaboration | Code review for infrastructure changes |
| Disaster recovery | Rebuild environments in minutes |
Core Concepts
| Concept | Description |
|---|---|
| Declarative | You describe what you want, not how to build it |
| Idempotent | Running the same code twice produces the same result |
| State | Tracks what resources exist and their configuration |
| Plan | Preview changes before applying them |
| Drift detection | Identify when reality differs from code |
Terraform vs CloudFormation: Choosing Your Tool
Overview Comparison
| Factor | Terraform | CloudFormation |
|---|---|---|
| Creator | HashiCorp (now IBM) | AWS |
| Language | HCL (HashiCorp Configuration Language) | YAML or JSON |
| Multi-cloud | Yes (AWS, Azure, GCP, etc.) | AWS only |
| State management | Self-managed or Terraform Cloud | AWS-managed |
| Drift detection | Community tools, limited native | Built-in |
| Learning curve | Moderate | Moderate |
| Community | Very large | Large (AWS-focused) |
When to Choose Terraform
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
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.
| Factor | Terraform | OpenTofu |
|---|---|---|
| License | BSL (commercial restrictions) | Open-source (MPL 2.0) |
| Compatibility | Original | Fork, compatible with Terraform |
| Community | HashiCorp-led | Linux Foundation, community-led |
| Enterprise features | Terraform Cloud/Enterprise | Community-driven alternatives |
For new projects: Consider OpenTofu if open-source licensing is important to your organisation.
Getting Started with Terraform
Installation
1# macOS (Homebrew)2brew tap hashicorp/tap3brew install hashicorp/tap/terraform45# Ubuntu/Debian6wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg7echo "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.list8sudo apt update && sudo apt install terraform910# Verify installation11terraform versionYour First Terraform Configuration
Create a file named main.tf:
1# Configure the AWS Provider2terraform {3 required_providers {4 aws = {5 source = "hashicorp/aws"6 version = "~> 5.0"7 }8 }9}1011provider "aws" {12 region = "ap-southeast-2" # Sydney13}1415# Create a VPC16resource "aws_vpc" "main" {17 cidr_block = "10.0.0.0/16"18 enable_dns_hostnames = true19 enable_dns_support = true2021 tags = {22 Name = "main-vpc"23 Environment = "production"24 ManagedBy = "terraform"25 }26}2728# Create a public subnet29resource "aws_subnet" "public" {30 vpc_id = aws_vpc.main.id31 cidr_block = "10.0.1.0/24"32 availability_zone = "ap-southeast-2a"33 map_public_ip_on_launch = true3435 tags = {36 Name = "public-subnet"37 }38}3940# Create an internet gateway41resource "aws_internet_gateway" "main" {42 vpc_id = aws_vpc.main.id4344 tags = {45 Name = "main-igw"46 }47}Terraform Workflow
1# 1. Initialise (download providers)2terraform init34# 2. Format code5terraform fmt67# 3. Validate syntax8terraform validate910# 4. Preview changes11terraform plan1213# 5. Apply changes14terraform apply1516# 6. Destroy when done (be careful!)17terraform destroyVariables and Outputs
variables.tf:
1variable "environment" {2 description = "Environment name"3 type = string4 default = "development"5}67variable "vpc_cidr" {8 description = "CIDR block for VPC"9 type = string10 default = "10.0.0.0/16"11}1213variable "instance_type" {14 description = "EC2 instance type"15 type = string16 default = "t3.micro"1718 validation {19 condition = can(regex("^t3\.", var.instance_type))20 error_message = "Instance type must be t3 family."21 }22}outputs.tf:
1output "vpc_id" {2 description = "ID of the VPC"3 value = aws_vpc.main.id4}56output "subnet_id" {7 description = "ID of the public subnet"8 value = aws_subnet.public.id9}Getting Started with CloudFormation
Your First CloudFormation Template
Create a file named vpc-stack.yaml:
1AWSTemplateFormatVersion: '2010-09-09'2Description: 'Basic VPC with public subnet'34Parameters:5 Environment:6 Type: String7 Default: development8 AllowedValues:9 - development10 - staging11 - production1213 VpcCidr:14 Type: String15 Default: '10.0.0.0/16'16 Description: CIDR block for the VPC1718Resources:19 MainVPC:20 Type: AWS::EC2::VPC21 Properties:22 CidrBlock: !Ref VpcCidr23 EnableDnsHostnames: true24 EnableDnsSupport: true25 Tags:26 - Key: Name27 Value: !Sub '${Environment}-vpc'28 - Key: Environment29 Value: !Ref Environment3031 PublicSubnet:32 Type: AWS::EC2::Subnet33 Properties:34 VpcId: !Ref MainVPC35 CidrBlock: '10.0.1.0/24'36 AvailabilityZone: !Select37 - 038 - !GetAZs ''39 MapPublicIpOnLaunch: true40 Tags:41 - Key: Name42 Value: !Sub '${Environment}-public-subnet'4344 InternetGateway:45 Type: AWS::EC2::InternetGateway46 Properties:47 Tags:48 - Key: Name49 Value: !Sub '${Environment}-igw'5051 AttachGateway:52 Type: AWS::EC2::VPCGatewayAttachment53 Properties:54 VpcId: !Ref MainVPC55 InternetGatewayId: !Ref InternetGateway5657Outputs:58 VpcId:59 Description: VPC ID60 Value: !Ref MainVPC61 Export:62 Name: !Sub '${Environment}-VpcId'6364 SubnetId:65 Description: Public Subnet ID66 Value: !Ref PublicSubnet67 Export:68 Name: !Sub '${Environment}-PublicSubnetId'CloudFormation Workflow
1# Validate template2aws cloudformation validate-template \3 --template-body file://vpc-stack.yaml45# Create stack6aws cloudformation create-stack \7 --stack-name my-vpc-stack \8 --template-body file://vpc-stack.yaml \9 --parameters ParameterKey=Environment,ParameterValue=development1011# Check stack status12aws cloudformation describe-stacks \13 --stack-name my-vpc-stack1415# Update stack16aws cloudformation update-stack \17 --stack-name my-vpc-stack \18 --template-body file://vpc-stack.yaml1920# Delete stack21aws cloudformation delete-stack \22 --stack-name my-vpc-stackStarter Templates
Terraform: Web Application Stack
1# main.tf - Complete web application infrastructure23terraform {4 required_version = ">= 1.0"5 required_providers {6 aws = {7 source = "hashicorp/aws"8 version = "~> 5.0"9 }10 }1112 # 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 = true18 dynamodb_table = "terraform-locks"19 }20}2122provider "aws" {23 region = var.aws_region2425 default_tags {26 tags = {27 Project = var.project_name28 Environment = var.environment29 ManagedBy = "terraform"30 }31 }32}3334# VPC Module (use official AWS module)35module "vpc" {36 source = "terraform-aws-modules/vpc/aws"37 version = "~> 5.0"3839 name = "${var.project_name}-vpc"40 cidr = var.vpc_cidr4142 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"]4546 enable_nat_gateway = true47 single_nat_gateway = var.environment != "production"48}4950# Application Load Balancer51resource "aws_lb" "main" {52 name = "${var.project_name}-alb"53 internal = false54 load_balancer_type = "application"55 security_groups = [aws_security_group.alb.id]56 subnets = module.vpc.public_subnets57}5859# Security Group for ALB60resource "aws_security_group" "alb" {61 name = "${var.project_name}-alb-sg"62 description = "Security group for ALB"63 vpc_id = module.vpc.vpc_id6465 ingress {66 from_port = 44367 to_port = 44368 protocol = "tcp"69 cidr_blocks = ["0.0.0.0/0"]70 }7172 ingress {73 from_port = 8074 to_port = 8075 protocol = "tcp"76 cidr_blocks = ["0.0.0.0/0"]77 }7879 egress {80 from_port = 081 to_port = 082 protocol = "-1"83 cidr_blocks = ["0.0.0.0/0"]84 }85}CloudFormation: Web Application Stack
1AWSTemplateFormatVersion: '2010-09-09'2Description: 'Web Application Infrastructure Stack'34Parameters:5 ProjectName:6 Type: String7 Description: Project name for resource naming8 Environment:9 Type: String10 AllowedValues: [development, staging, production]11 VpcCidr:12 Type: String13 Default: '10.0.0.0/16'1415Resources:16 # VPC17 VPC:18 Type: AWS::EC2::VPC19 Properties:20 CidrBlock: !Ref VpcCidr21 EnableDnsHostnames: true22 EnableDnsSupport: true23 Tags:24 - Key: Name25 Value: !Sub '${ProjectName}-${Environment}-vpc'2627 # Public Subnets28 PublicSubnet1:29 Type: AWS::EC2::Subnet30 Properties:31 VpcId: !Ref VPC32 CidrBlock: '10.0.1.0/24'33 AvailabilityZone: !Select [0, !GetAZs '']34 MapPublicIpOnLaunch: true3536 PublicSubnet2:37 Type: AWS::EC2::Subnet38 Properties:39 VpcId: !Ref VPC40 CidrBlock: '10.0.2.0/24'41 AvailabilityZone: !Select [1, !GetAZs '']42 MapPublicIpOnLaunch: true4344 # Internet Gateway45 InternetGateway:46 Type: AWS::EC2::InternetGateway4748 AttachGateway:49 Type: AWS::EC2::VPCGatewayAttachment50 Properties:51 VpcId: !Ref VPC52 InternetGatewayId: !Ref InternetGateway5354 # Application Load Balancer55 ApplicationLoadBalancer:56 Type: AWS::ElasticLoadBalancingV2::LoadBalancer57 Properties:58 Name: !Sub '${ProjectName}-${Environment}-alb'59 Subnets:60 - !Ref PublicSubnet161 - !Ref PublicSubnet262 SecurityGroups:63 - !Ref ALBSecurityGroup64 Scheme: internet-facing65 Type: application6667 # ALB Security Group68 ALBSecurityGroup:69 Type: AWS::EC2::SecurityGroup70 Properties:71 GroupDescription: Security group for ALB72 VpcId: !Ref VPC73 SecurityGroupIngress:74 - IpProtocol: tcp75 FromPort: 44376 ToPort: 44377 CidrIp: 0.0.0.0/078 - IpProtocol: tcp79 FromPort: 8080 ToPort: 8081 CidrIp: 0.0.0.0/08283Outputs:84 VPCId:85 Value: !Ref VPC86 Export:87 Name: !Sub '${ProjectName}-${Environment}-VPCId'88 ALBDNSName:89 Value: !GetAtt ApplicationLoadBalancer.DNSName90 Export:91 Name: !Sub '${ProjectName}-${Environment}-ALBDNSName'Best Practices
Project Structure
1infrastructure/2├── environments/3│ ├── dev/4│ │ ├── main.tf5│ │ ├── variables.tf6│ │ └── terraform.tfvars7│ ├── staging/8│ └── production/9├── modules/10│ ├── vpc/11│ ├── ecs/12│ └── rds/13├── shared/14│ └── backend.tf15└── README.mdEssential Practices
| Practice | Why It Matters |
|---|---|
| Use remote state | Enable team collaboration, prevent conflicts |
| Lock state files | Prevent concurrent modifications |
| Use modules | Reusable, tested components |
| Environment separation | Isolated configurations per environment |
| Review before apply | Always run plan first |
| Tag everything | Track costs, ownership, lifecycle |
| Encrypt sensitive data | Never commit secrets to version control |
| CI/CD integration | Automated, 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
planoutput for unintended changes - Use
prevent_destroyfor critical resources
Common Mistakes to Avoid
| Mistake | Consequence | Prevention |
|---|---|---|
| Local state | Team conflicts, lost state | Use remote backend from day one |
| Hardcoded values | Can't reuse across environments | Use variables for everything |
| No modules | Copy-paste, drift between environments | Extract common patterns to modules |
| Skipping plan | Unexpected destructions | Always review plan before apply |
| Manual changes | Drift, broken state | Never modify resources outside IaC |
| No tagging | Can't track costs or ownership | Enforce tags via policies |
Getting Help
Learning Resources
| Resource | Type | Best For |
|---|---|---|
| Terraform docs | Documentation | Reference, getting started |
| CloudFormation docs | Documentation | AWS-specific patterns |
| HashiCorp Learn | Interactive tutorials | Hands-on practice |
| AWS Workshop Studio | Guided workshops | CloudFormation 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
Comments
Sign in to join the conversation
LoginNo comments yet. Be the first to share your thoughts!
Found an issue with this article?
