Every few years, the software industry coalesces around an idea that sounds obvious in retrospect.
Infrastructure as Code, treating servers, networks, and cloud resources the same way you treat application code, is one of those ideas.
The phrase has become a buzzword, which means it is simultaneously overused and under-explained.
The Problem IaC Solves
Before IaC, infrastructure management looked something like this:
- Someone logs into the AWS console.
- Clicks through the UI to create resources.
- Writes down (maybe) what they did in a wiki page.
- Six months later, someone else needs to recreate that environment.
- They click through the UI again, slightly differently.
- The environments are now different, and nobody knows why.
This approach, click ops, is slow, error‑prone, and impossible to audit.
If a production environment goes down at 3 AM, you do not want to be clicking through a GUI while your users see errors.
Infrastructure as Code replaces the manual process with code that describes what the infrastructure should look like.
Because the description lives in source files, you can version it, compare revisions, and roll back if needed.
The code lives in version control.
Changes go through review, so peers can spot mistakes before they affect a live system.
Deployments are automated, eliminating the need for ad‑hoc manual steps.
The result is repeatable, auditable, and self‑documenting, which means you can prove exactly how a environment was built at any point in time.
What "Code" Actually Means
IaC is not just any code that touches infrastructure.
A key property is idempotency: running the code should produce the same result, regardless of how many times you run it.
If your IaC script says “there should be three web servers,” running it once creates three servers.
Running it again, it should find the three existing servers and do nothing, rather than creating three more.
This matters because real infrastructure changes over time; servers are patched, scaled, or decommissioned, and you need a stable target state.
Your code needs to converge toward the desired state rather than accumulating duplicates, which prevents resource bloat and unexpected costs.
Declarative vs Imperative
There are two approaches to IaC:
Declarative: You describe the desired end state. “There should be a VPC with CIDR 10.0.0.0/16.” The tool figures out how to get there.
# Terraform (declarative)
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = { Name = "production-vpc" }
}
Imperative: You write a sequence of commands. “First create a VPC, then create subnets, then create security groups.” The tool follows your steps.
# Ansible (imperative, but with idempotent modules)
- name: Create VPC
community.aws.ec2_vpc_net:
name: production-vpc
cidr_block: 10.0.0.0/16
state: present
Terraform is one of the most widely used declarative tools. It lets you express what you want without worrying about the exact order of API calls.
Ansible defaults to imperative but achieves idempotency through its module system, so each task can be run repeatedly without side effects.
The Tools That Matter
Terraform
HashiCorp's Terraform [1] is the dominant IaC tool for cloud infrastructure.
Its strength is provider coverage: Terraform can manage AWS, Azure, GCP, and hundreds of other services through a unified interface.
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id]
user_data = file("init.sh")
}
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Terraform maintains a state file that tracks what it has created.
This state is the source of truth between your code and reality, allowing Terraform to know which resources already exist.
Managing state correctly, using remote backends, state locking, and proper isolation, is the most important Terraform skill, because a corrupted or out‑of‑date state file can cause accidental destruction or duplication of resources.
Strengths:
- Multi‑cloud by design, so the same workflow works across providers.
- Large ecosystem of providers and modules, which accelerates adoption.
- Plan phase shows changes before applying, giving you a safety net.
- Mature and widely adopted, meaning you’ll find plenty of community support.
Challenges:
- State management complexity; you must configure remote storage and locking to avoid race conditions.
- Learning curve for HCL (HashiCorp Configuration Language), which is a domain‑specific language rather than a general‑purpose one.
- Not a configuration manager; you often pair Terraform with tools like Ansible to handle software installation.
Ansible
Red Hat's Ansible [2] takes a different approach. Rather than declaring desired state, Ansible modules are idempotent tasks that configure existing systems.
- name: Install and configure Nginx
hosts: webservers
become: yes
tasks:
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: yes
- name: Copy Nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart Nginx
- name: Ensure Nginx is running
ansible.builtin.service:
name: nginx
state: started
Ansible is agentless, it connects to targets over SSH. This makes it immediately usable in most environments without deploying agents, which reduces operational overhead.
Strengths:
- Agentless architecture, so you can manage any reachable host.
- Human‑readable YAML syntax, lowering the barrier for newcomers.
- Massive module library, covering everything from networking to cloud services.
- Excellent for configuration management, allowing you to install packages, edit files, and start services in one playbook.
Challenges:
- State management is implicit (no plan phase), so you rely on the playbook’s logic to avoid unintended changes.
- Can be slower than Terraform for large provisioning tasks because it executes tasks sequentially over SSH.
- YAML verbosity can make complex playbooks harder to follow.
Pulumi
Pulumi [3] takes a radical approach: you write infrastructure in general‑purpose programming languages, Python, TypeScript, Go, C#, Java.
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("production", {
cidrBlock: "10.0.0.0/16",
tags: { Name: "production-vpc" },
});
const subnet = new aws.ec2.Subnet("public", {
vpcId: vpc.id,
cidrBlock: "10.0.1.0/24",
tags: { Name: "production-public" },
});
Strengths:
- Real programming language features: loops, functions, conditionals, which let you abstract and reuse logic elegantly.
- Share code between application and infrastructure, reducing duplication and keeping both layers in sync.
- Excellent for developers already comfortable with those languages, because no new DSL is required.
Challenges:
- Steeper learning curve for operations teams that are accustomed to declarative DSLs.
- State management is newer than Terraform's, so tooling and best‑practice guidance are still evolving.
- Smaller community than Terraform, meaning fewer pre‑built components and less peer support.
The IaC Workflow
Day 0: Define
You write code describing your infrastructure.
You put it in version control alongside your application code, so a single repository reflects the whole system.
git clone git@github.com:myorg/infrastructure.git
cd infrastructure/production
terraform init
Day 1: Provision
terraform plan # See what will be created
terraform apply # Create the infrastructure
The plan phase is one of Terraform’s most useful features.
It previews the intended changes before they are applied, letting you catch accidental deletions or misconfigurations early.
Click‑based workflows can provide previews in some platforms, but not with the same code‑reviewable, provider‑independent workflow that Terraform offers.
Day 2+: Maintain
Infrastructure evolves.
You modify the code and run terraform apply again.
# Adding a new subnet
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
tags = { Name = "production-private" }
}
Terraform's state tracks the current state.
It compares the code to the state, computes the diff, and applies only the changes needed, which prevents unnecessary churn and keeps the environment stable.
Drift Detection
One of the most valuable IaC features is drift detection: identifying when actual infrastructure has drifted away from what the code specifies.
terraform plan
# ± aws_subnet.private
# tags.Name: "production-private" => "manual-tag-from-console"
Someone changed a tag directly in the AWS console.
Terraform shows you the drift so you can either update the code or revert the manual change, preserving the single source of truth.
Why This Matters for Your Organisation
Reproducibility
Code creates infrastructure identically every time.
Your production environment, staging, and development can all be defined by the same code with different variables, ensuring consistency across the pipeline.
Speed
A cloud resource that takes 20 minutes through the UI takes seconds through code.
A full environment that takes days to build manually takes minutes with IaC, dramatically shortening release cycles and reducing time‑to‑value.
Auditability
Every change to infrastructure is visible in git history.
Code review, automated testing, and approval workflows apply to infrastructure exactly as they apply to application code, giving you a transparent change‑management process.
Disaster Recovery
When a primary region goes down, infrastructure defined in code can help you recreate an environment in another region; recovery still depends on state, data backups, provider limits, and tested procedures.
IaC turns disaster recovery from a multi‑day manual ordeal into an automated process that can be triggered with a single command.
Security and Compliance
Policy‑as‑code tools like Open Policy Agent (OPA) or HashiCorp Sentinel can automatically validate that your infrastructure meets security requirements before applying changes.
# OPA policy: no public S3 buckets
package terraform.analysis
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
resource.change.after.acl == "public-read-write"
msg := sprintf("S3 bucket %v must not be public", [resource.name])
}
Common Pitfalls
State File Management
Treat the state file as the most critical artifact in your infrastructure.
Store it in remote backends (S3 with DynamoDB locking, Terraform Cloud, etc.) so that multiple team members can work safely without overwriting each other’s changes.
Never commit it to version control, because the file contains sensitive identifiers and can quickly become out‑of‑sync with reality.
Secret Management
Do not hardcode secrets in IaC code.
Use secret managers: AWS Secrets Manager, HashiCorp Vault, or environment variables injected at runtime, which keeps credentials out of the repository and allows rotation without code changes.
# Bad: secret in code
database_password = "super-secret-password"
# Good: reference to secret manager
database_password = data.aws_secretsmanager_secret.db_password.value
Module Reuse Without Understanding
Terraform modules from the Registry are convenient but understand what they do before deploying them to production.
Modules with bugs or security issues will propagate those issues everywhere they are used, so a quick audit of inputs, outputs, and underlying resources saves you future headaches.
The Bigger Picture
Infrastructure as Code represents a philosophical shift: infrastructure is a product, not a project.
Infrastructure defined in code can be versioned, tested, reviewed, and improved, just like software.
The alternative, manual infrastructure management, leads inevitably to snowflake servers, environment drift, and knowledge concentrated in the heads of a few engineers who “know how it is set up.”
IaC does not eliminate complexity, but it makes complexity manageable.
Your infrastructure becomes a first‑class artifact that the whole team can understand, review, and improve.

Comments (0)
Please sign in to leave a comment