Infrastructure as Code

Infrastructure as code (IaC) means describing infrastructure in files that are versioned, reviewed and applied by a tool, instead of being clicked together in a console. The point is not the files; it is what they make possible:

  • Reproducibility — the same definition builds staging and production, and rebuilds either after a disaster.
  • Review — a change to a firewall rule is a pull request with a diff, not something discovered later.
  • History — who changed what, when and why is in Git.
  • Automation — environments can be created and destroyed by pipelines.

The distinctions that matter

Declarative or imperative

DeclarativeImperative
You writeThe desired end stateThe steps to get there
The toolWorks out the difference and what to changeRuns your steps in order
Re-runningSafe: nothing changes if reality already matchesSafe only if every step was written to be idempotent
ExamplesTerraform, OpenTofu, CloudFormation, Kubernetes manifestsShell scripts, most CLI automation

General-purpose-language tools such as Pulumi and the CDK look imperative but are declarative underneath: the program builds a desired-state graph, and an engine diffs it.

Provisioning or configuration

LayerQuestionTypical tools
ProvisioningWhich cloud resources exist?Terraform, OpenTofu, CloudFormation, Pulumi
Image buildingWhat is baked into a machine image?Packer, container builds
ConfigurationWhat is installed and running on a machine?Ansible, cloud-init
WorkloadWhat runs on the platform?Kubernetes manifests, Helm, Kustomize

The layers blur — Terraform can run scripts, Ansible can create cloud resources — but each tool is much better at its own layer.

Mutable or immutable

A mutable server is patched and reconfigured in place over its life. An immutable one is never changed: a new image is built and the server is replaced. Immutable infrastructure removes configuration drift and makes rollback trivial, at the cost of needing a build pipeline and stateless servers. Containers made this the default for applications; node images and auto scaling groups extend it to the hosts.

Plan-and-apply or continuous reconciliation

Plan and applyContinuous reconciliation
RunsWhen someone or a pipeline triggers itAll the time, in a control loop
DriftDetected at the next planCorrected automatically
PreviewAn explicit plan to reviewUsually none
ExamplesTerraform, OpenTofu, CloudFormationKubernetes controllers, Crossplane, ACK, Argo CD

A reviewed plan is valuable for foundations where a mistake is expensive. Continuous reconciliation suits resources that follow an application’s lifecycle and are requested self-service.

State

A declarative tool needs to know which real resources belong to which definitions. Kubernetes keeps that in its API server. Terraform and OpenTofu keep it in a state file, and how that file is stored, locked and split determines whether a team can work safely. That deserves its own note: state and collaboration.

Choosing tools

SituationReasonable choice
Cloud foundations: accounts, networks, clusters, databasesTerraform or OpenTofu
AWS-only shop that wants native support and no state fileCloudFormation or CDK
Developers who want loops, types and tests in a real languagePulumi or CDK
Configuring VMs, network devices, or anything reached over SSHAnsible
Golden machine imagesPacker
Self-service infrastructure through the Kubernetes APICrossplane, ACK, kro
Kubernetes workloadsHelm or Kustomize, delivered by GitOps

Most organisations end up with two or three of these, one per layer. The mistake is using one tool for every layer because it is the one the team knows.

Practices that apply to all of them

  1. Everything through version control and a pipeline. Console changes are for emergencies and are back-ported immediately.
  2. Review the plan, not only the code. The diff of what will happen in the real account is what matters.
  3. Small blast radius. Split definitions so one mistake cannot touch everything; separate environments and separate lifecycles.
  4. No secrets in code or state where avoidable. Reference a secret store. See secrets detection.
  5. Policy as code. Check plans against rules — encryption on, no public buckets, mandatory tags — before apply, with OPA/Conftest, Checkov or Sentinel. Part of DevSecOps.
  6. Test. Static validation and linting on every commit; plan in CI; ephemeral environments for modules that matter.
  7. Detect drift on a schedule and treat it as a defect.
  8. Pin versions of tools, providers and modules, and upgrade deliberately.
  9. Tag everything with owner, environment and cost centre — see cost allocation tags.

Notes in this section

Further reading