What is Infrastructure as Code?
Infrastructure as Code (IaC) means describing your servers, networks, and cloud resources in plain text files that you keep in version control (a system like Git that tracks every change to your files), instead of setting them up by clicking around in a web console or typing commands by hand. You write down what you want — “an Ubuntu 24.04 server with Nginx installed and the firewall configured” — and a tool reads that file and makes reality match it. This page explains why that matters, the problems it solves, and the two big families of tools you will meet.
The problem: click-ops and snowflake servers
Before IaC, you built a server by hand. You logged in, ran sudo apt install nginx, edited a config file in /etc/nginx, opened a firewall port with ufw, and tweaked things until the site worked. This style of work is nicknamed click-ops (configuring infrastructure by clicking through a web dashboard or typing one-off commands).
It feels fast for one server. It falls apart at scale, and here is why:
- Snowflake servers. A “snowflake” is a server that was hand-built and is now subtly unique — nobody can fully reproduce it. Six months later you have forgotten which packages you installed and which config lines you changed. If the server dies, you cannot rebuild it exactly.
- Configuration drift. Over time, manual fixes pile up. Two servers that started identical slowly drift apart (grow small, undocumented differences) because someone patched one at 2am and not the other. Bugs then appear on one machine and not the next, and they are miserable to debug.
- No record. A click leaves no trace. There is no history of who changed what and why, so you cannot review, audit, or undo a change.
- It does not scale. Building 1 server by hand is fine. Building 50 identical servers by hand is slow, boring, and guarantees mistakes.
The solution: describe infrastructure in code
With IaC, the truth about your infrastructure lives in files. Those files are the single source of truth, and the tooling makes the real world match them. Because the files are just text, you get every benefit that software engineers already enjoy with application code:
- Repeatable. Run the same file and get the same result, every time, on every server. Rebuilding a lost machine is one command, not a day of guesswork.
- Reviewable. Changes go through a pull request (a proposed change others can read and approve before it merges). A teammate can see exactly what will change before it touches production.
- Versioned. Git keeps full history. You can see who changed a firewall rule last Tuesday and roll it back if it broke something.
- Self-documenting. The file is the documentation. There is no separate, out-of-date wiki page to trust.
- Testable and automated. The same files can run automatically in a CI/CD pipeline (a system that runs your code and deploys it automatically), so humans stop hand-typing commands on live servers.
A tiny example
Here is the difference in spirit. The manual way:
sudo apt update
sudo apt install -y nginx
sudo ufw allow 'Nginx HTTP'
sudo systemctl enable --now nginx
You run those by hand, once, and hope you remember them later. The IaC way describes the same outcome declaratively (you state the desired end state, not the steps) in an Ansible playbook:
- hosts: webservers
become: true
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: true
- name: Allow HTTP through the firewall
community.general.ufw:
rule: allow
name: Nginx HTTP
- name: Ensure Nginx is running and starts at boot
service:
name: nginx
state: started
enabled: true
This file is checked into Git. Anyone can read it, review it, and run it against one server or a hundred.
Provisioning vs configuration management
IaC tools fall into two broad jobs. Most teams use one of each, because they solve different halves of the problem.
Provisioning means creating the infrastructure itself — spinning up the actual virtual machine, the network, the load balancer, the database instance, often in a cloud like AWS. You are bringing resources into existence from nothing.
Configuration management means setting up what runs inside a machine that already exists — installing packages, writing config files, starting services. You are shaping a server you already have.
| Provisioning | Configuration management | |
|---|---|---|
| Job | Create infrastructure (VMs, networks, cloud resources) | Configure the inside of existing servers |
| Typical tool | Terraform, OpenTofu, Pulumi | Ansible, Chef, Puppet |
| Works against | Cloud APIs (AWS, GCP, Azure) | Servers over SSH |
| Style | Declarative (state the end goal) | Mostly declarative, some procedural |
| Example | ”Create 3 Ubuntu VMs and a load balancer" | "Install Nginx and deploy the site config” |
The most common modern setup: use Terraform to create the servers in the cloud, then use Ansible to configure what runs on them. They are partners, not rivals, though their jobs overlap a little.
Never put secrets (passwords, API keys, private keys) directly in IaC files. They get committed to Git history forever and are trivial to leak. Use a secrets tool such as Ansible Vault, HashiCorp Vault, or your cloud’s secrets manager, and reference the secret rather than pasting it.
When to use IaC (and when not to)
Use IaC when you have more than one server, when a team shares the infrastructure, when you must rebuild reliably after a failure, or when changes need review and an audit trail. That is almost every real production system.
You can skip it for a throwaway experiment on a single box you will delete tomorrow, or while you are still learning a tool by hand for the first time. Even then, writing down the commands in a script is a cheap first step toward full IaC.
Best practices
- Keep all IaC files in Git, and make every change through a reviewed pull request rather than editing live servers.
- Treat servers as cattle, not pets — rebuild them from code instead of hand-fixing them, so no snowflakes form.
- Store secrets in a dedicated secrets manager, never in the repository.
- Run your IaC the same way in every environment (dev, staging, production) so behaviour stays consistent.
- Pin tool and provider versions so a surprise upgrade does not change your infrastructure unexpectedly.
- Use provisioning tools (Terraform) to build resources and configuration tools (Ansible) to set them up, and let each do its own job.