A 32-minute working session on describing servers, networks, and databases as text you commit to git — so a whole environment becomes something you can plan, review, and rebuild instead of click together by hand and pray.
Every cloud account starts the same way: someone clicks through a console, wires up a server and a database, and it works. Six months later nobody remembers exactly what they clicked, the staging environment drifted from production, and rebuilding from scratch is a day of archaeology. IaC turns all of that into a file you can read, review, and re-run.
One console session makes one snowflake. One file makes as many identical environments as you need.
Spin up an identical environment in minutes — dev, staging, a disaster-recovery region — from the same source.
Infrastructure changes arrive as pull requests. A teammate reads the diff before it touches production.
Every change is a commit. You can see who changed what, when, and why — and roll back like any other code.
The files are the documentation. No stale wiki describing a console nobody opens.
There are two ways to tell a computer to build something. You can list the exact steps in order (imperative), or you can describe the end result and let the tool figure out the steps (declarative). Almost all modern IaC is declarative — and that choice is what makes re-running safe.
The engine reconciles desired against real and acts only on the gap — that is what makes a second run safe.
Here is the question that confuses every newcomer: your code says "one server", the cloud has a server — but how does the tool know thatserver is the one your code created, and not someone else's? The answer is state: a record that maps each resource in your code to a real object in the cloud.
State is the lookup table: the web resource in your code is the cloud object i-0ab9.
Someone resized the box by hand. The next plan spots the mismatch and offers to pull reality back to the code.
import them into state so the tool starts tracking them.The single best habit IaC gives you: nothing changes until you have read a preview of exactly what will change. The core loop is plan (dry run — show me the diff) then apply(do it). That preview is what turns "hope this works" into a reviewable, boring deploy.
The everyday loop: write → init → plan → apply, repeating on every change. destroy tears the whole thing down cleanly when you are done.
A resource in your code that does not exist yet. The tool will create it.
A resource that exists but differs. Watch for the ones that replace rather than edit in place — that means downtime.
A resource the code no longer wants. Read these lines hardest — a stray destroy can delete a database.
Once you have one environment described in code, you will want a second that is almost the same. Copy-pasting the files is the trap — now every fix has to be made in three places. A module packages a chunk of infrastructure once and lets you stamp it out with different inputs.
A root module wires together child modules; each child is reused across every environment with different inputs.
A module takes variables (size, region, name) and exposes outputs (the database endpoint) so callers can wire results together — exactly like a function signature.
A module wrapping a single resource adds indirection for no gain. Reach for one when a group of resources repeats — not by reflex.
The Terraform / OpenTofu Registry has battle-tested modules (VPCs, EKS clusters). Pin a version and read the code before trusting it in production.
Like a blueprint for a standard room — draw it once, then build it on three floors with different paint and furniture. Spinning up a Kubernetes cluster is a classic job for a well-tested module rather than hand-written resources.
The tools mostly split on two questions: do you write a dedicated configuration language or a real programming language, and are you tied to one cloud or many? None is "best" — each is the right call in a different spot. Here are the honest trade-offs.
Declarative HCL. The industry default, with the largest provider ecosystem — it can manage almost any cloud or SaaS, not just AWS.
Pro — multi-cloud, huge module registry, declarative and readable; the de-facto standard.
Con — HCL is its own language with limited logic; complex conditionals get awkward.
Choose when you want a portable, declarative standard across more than one provider — the safe default.
You write infrastructure in a general-purpose language, but the engine still works declaratively — state, plan, and apply, just like Terraform underneath.
Pro — full language power: loops, functions, types, tests, IDE autocomplete; great for complex logic.
Con — that power invites complexity; smaller ecosystem; the team must know the language.
Choose when developers own infra and want real code — abstractions, unit tests, and shared libraries.
CloudFormationis AWS's native declarative templates; CDK lets you write real code that generates those templates. AWS stores the state for you.
Pro — deep, day-one AWS integration; AWS manages state and rollbacks; no extra tooling.
Con — AWS-only, full lock-in; raw templates are verbose; updates can be slow.
Choose when you are all-in on AWS and want a first-party, fully-managed tool with no third party.
Ansible is task-based and leans imperative (a list of steps), though good modules are idempotent. Its sweet spot is configuring machines, not provisioning cloud resources.
Pro — agentless (just SSH), simple YAML, excellent for installing and configuring software.
Con — weak at managing cloud resource lifecycles; no real state/plan model like Terraform.
Choose for in-server configuration — often paired with Terraform: provision with one, configure with the other.
Terraform / OpenTofu and CloudFormation: you state the end result. Easier to read, safer to re-run, the natural fit for provisioning cloud resources. This is where most teams should start.
Real-language tools (Pulumi, CDK) and imperative ones (Ansible) give you more power and flexibility — at the cost of more ways to write something clever that the next person can't follow. Reach for power only when a plain config language genuinely can't express it.
IaC pays off when the code is the single source of truth and nobody edits production by hand. Three practices get you there: store state remotely and safely, keep secrets out of the files, and run the whole thing through a pipeline so every change is reviewed.
Store state in a shared backend — an S3 bucket, Azure Blob, GCS, or a managed service like HCP Terraform — with lockingso two applies can't run at once, and versioning so you can recover. Never commit state to git.
Don't paste passwords into .tf files. Pull them from a secrets manager (Vault, AWS Secrets Manager) at run time. Remember state can hold secrets in plain text — so encrypt the backend and lock down who can read it.
Run plan automatically on every pull request, post the diff for review, and apply only after merge — never from a laptop. See Developer Tooling for the pipeline mechanics.
The PR is where the plan is reviewed; the merge is what triggers apply. No human touches production directly.
Five quick questions on declarative IaC, state, drift, plan/apply, and the tooling landscape — instant feedback, no sign-in.
Navigate with ← → or scroll · back to library