Infrastructure as Code (IaC) lets you manage servers, networks and cloud resources with code instead of clicking around in dashboards. In this post we’ll see why IaC matters, how Terraform works, and walk through a simple example you can try in your own cloud account.
Why Infrastructure as Code matters
A few years ago, provisioning servers usually meant:
Log in to a cloud console
Click through multiple screens
Create VMs, networks, security groups
Hope you didn’t forget a step
That works for a single environment. But it quickly becomes painful when you need:
Multiple environments (dev / staging / production)
Consistent setups for different regions or clients
Easy rollback when something breaks
Version history of who changed what and when
That’s where Infrastructure as Code (IaC) comes in.
With IaC, your infrastructure is described in plain text files and stored in version control (Git). You review, test and deploy infra changes the same way you handle application code.
Benefits:
✅ Repeatable: you can recreate the same environment with one command
✅ Reviewable: infrastructure changes go through pull requests
✅ Auditable: every change is tracked in Git history
✅ Automatable: you can build CI/CD pipelines for infra deployments
There are multiple IaC tools (CloudFormation, ARM/Bicep, Pulumi, Ansible, etc.), but in this article we’ll focus on Terraform, because it’s cloud-agnostic and widely used.
What is Terraform?
Terraform is an open-source IaC tool from HashiCorp. In simple terms:
Terraform reads configuration files → talks to cloud providers → creates or updates resources to match that configuration.
Key concepts:
Provider – plugin that knows how to talk to a specific platform (AWS, Azure, GCP, Cloudflare, GitHub, etc.)
Resource – an actual object you want to manage (VM, storage account, network, DNS record…)
State – Terraform’s memory of what it has already created
Plan – a preview of what will change before anything is actually modified
Terraform files use the HCL syntax (HashiCorp Configuration Language). It’s declarative: you describe the end state, and Terraform figures out how to get there.
Basic Terraform workflow
The usual workflow looks like this:
Write configuration in
.tffilesRun
terraform initRun
terraform planand review the changesRun
terraform applyto create/update resourcesLater, change the
.tffiles and repeat plan/applyUse
terraform destroyif you want to tear everything down
Let’s look at a minimal example.
Example: Create a simple VM (conceptual example)
You can adapt this to any cloud provider you use (Azure, AWS, GCP). I’ll show a simple AWS-style configuration so the idea is clear.
Folder structure:
my-terraform-project/
main.tf
variables.tf
outputs.tf
1. Define the provider (main.tf)
terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
required_providerstells Terraform which plugins to use.provider "aws"configures the region (coming from a variable).
2. Define a resource (main.tf continued)
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
tags = {
Name = "hyaking-demo-web"
}
}
Here we define a single EC2 instance (virtual machine):
aws_instanceis the resource type"web"is the local name we can reference elsewhereami,instance_typeetc. can be variables or hard-coded values
3. Variables (variables.tf)
variable "aws_region" {
description = "AWS region to deploy into"
type = string
default = "eu-central-1"
}
variable "ami_id" {
description = "Base image for the VM"
type = string
}
variable "instance_type" {
description = "VM size"
type = string
default = "t3.micro"
}
You can specify ami_id from a terraform.tfvars file or via CLI.
Example terraform.tfvars:
ami_id = "ami-0123456789abcdef0"
4. Outputs (outputs.tf)
output "instance_id" {
description = "ID of the created EC2 instance"
value = aws_instance.web.id
}
output "public_ip" {
description = "Public IP address of the instance"
value = aws_instance.web.public_ip
}
After terraform apply, Terraform will display these output values.
Running the configuration
In a terminal inside my-terraform-project/:
# 1. Initialize Terraform, download providers
terraform init
# 2. See what will be created
terraform plan
# 3. Apply the changes (actually create resources)
terraform apply
Terraform will:
Read your
.tffilesCompare desired state vs. current cloud state
Show you a plan like: “+ create aws_instance.web”
Ask you to confirm (
yes)Create the VM and record details in the state file
The state file (terraform.tfstate) is critical: it maps your resources in code to real resources in the cloud. In bigger teams you should store it in a remote backend (like S3 + DynamoDB, Azure Storage, etc.) instead of keeping it only on a laptop.
idempotence: run it again, nothing should change
A big advantage of Terraform (and IaC in general) is idempotence:
If your code hasn’t changed and you run
terraform applyagain, Terraform should do nothing.
It will print something like:
No changes. Your infrastructure matches the configuration.
If you edit the code (for example, change instance_type from t3.micro to t3.small) and then run plan/apply, Terraform will update only what’s necessary.
Organising Terraform code for real projects
The tiny example above is nice for learning, but real infra gets complex quickly. A few best practices:
1. Separate environments
Use different folders or workspaces for dev, staging, prod:
infra/
dev/
main.tf
variables.tf
staging/
prod/
Each environment can have different values (sizes, counts, regions) while sharing common modules.
2. Use modules
Terraform modules are reusable collections of resources.
Example structure:
modules/
vpc/
main.tf
variables.tf
outputs.tf
web_app/
main.tf
variables.tf
outputs.tf
envs/
dev/
main.tf # calls module "vpc" and "web_app"
prod/
main.tf
This lets you reuse the same building blocks (VPC, DB, app server) with different parameters.
3. Version control everything
Keep your
.tffiles in Git (GitHub / GitLab etc.)Use pull requests and code reviews for infra changes
Tag releases or use branches for different infra versions
4. Use a remote backend early
For team usage:
Configure a remote backend (S3, Azure Storage, GCS, Terraform Cloud, etc.)
Enable state locking, so two people don’t apply changes at the same time
Terraform + CI/CD
Once your Terraform project is in Git, you can automate:
Run
terraform fmtandterraform validateon each pushRun
terraform planfor pull requests and post the plan as a PR commentRequire manual approval before running
terraform applyfrom your CI/CD pipeline
This makes infra changes visible and auditable:
“Who changed the database subnet?” → Check Git history
“When did we add this new VM?” → See merge commit + plan output
Most CI systems (GitHub Actions, GitLab CI, Azure DevOps, Bitbucket Pipelines) have Terraform templates or examples you can adapt easily.
Common mistakes when starting with Terraform
Editing resources manually in the cloud console
If you change something directly in the portal, Terraform’s state won’t know about it.
Always try to make changes in the code first, then run
apply.
Committing the state file to Git
Never commit
terraform.tfstate(or its backup) to a public repo.Add it to
.gitignoreif you’re not using a remote backend yet.
Using one big state for everything
Start small. Use separate projects/state for unrelated parts of infra, otherwise one apply can affect too much.
Not using variables and outputs
Hard-coding all values makes reuse and environment differences difficult.
Variables + outputs make modules composable and easier to manage.
When NOT to use Terraform
Terraform is great, but not mandatory for every scenario. It might be overkill when:
You only have one tiny server and never plan to add more.
You use a Platform as a Service (e.g. simple static site host) with almost zero infra.
Your team is extremely small and not comfortable with code yet.
However, as soon as you have multiple environments or multiple services, using Terraform usually pays off quickly.
Final thoughts
Infrastructure as Code changes the way teams think about servers and networks. Instead of fragile, manually configured environments, you get:
Reproducible infrastructure
Clear git history for every change
Easier collaboration between dev and ops teams
Terraform is one of the most popular tools in this space. Start small:
Pick a non-critical resource (like a test VM or a dev VPC)
Write a simple Terraform configuration
Run
init → plan → applyDestroy it when you’re done
Once you’re comfortable, you can grow into modules, remote state, and CI/CD pipelines.
If you’re just getting started with DevOps/IaC, Terraform is a great tool to have in your toolkit—and your future self will thank you when you can recreate an entire environment with a single command.






