Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Infrastructure as Code Overview

Infrastructure as Code (IaC) is the practice of managing and provisioning infrastructure through machine-readable definition files instead of console clicks, tickets, and one-off shell scripts. The moment infrastructure is expressed as code it inherits every property that makes software manageable: it lives in version control, it is reviewed in pull requests, it can be tested, linted, scanned, and reproduced on demand. Interviewers use IaC questions to probe whether you understand the invariants — idempotency, state, convergence, drift — rather than the syntax of one tool. This page is the landscape map; the deep dives are Terraform (provisioning), Ansible (configuration management), and Beyond Terraform (OpenTofu, Pulumi, CDK, Crossplane). A consolidated question bank lives in IaC Interview Questions.

Why IaC Exists

Manual infrastructure fails in three predictable ways, and IaC exists to eliminate each one:

  • Drift — live systems are mutated in place: someone opens port 443 in the console during an incident and never backports it. Months later staging and prod are different animals, and “it worked in staging” becomes a lie. IaC makes declared config the source of truth and turns drift into a detectable diff (terraform plan) instead of an archaeology project.
  • Repeatability — without code every environment is a snowflake built by whoever was on call that week. Rebuilding a region after an outage, standing up a compliance-test environment, or expanding to a new region become multi-day human projects. With IaC they are a re-run of a reviewed pipeline.
  • Review culture — console changes have no diff, no reviewer, no history, and no rollback plan. A pull request that changes one security-group rule gets reviewed by the networking owner before it merges, not flagged in an audit after it ships. IaC moves infrastructure onto the same collaboration rails as application code: branching, review, CI gates, tagged releases.

The economic summary interviewers like: IaC trades a one-time learning-and-migration cost for a permanently auditable, reproducible, cheaper-to-operate estate — “infrastructure gets the same lifecycle as software.”

Imperative vs Declarative

The deepest divide in the tool landscape is who computes the path from current state to desired state:

AspectImperativeDeclarative
Approach“How to do it”“What I want”
ExampleBash scripts, PulumiTerraform, CloudFormation
StateYou manageTool manages
IdempotencyManualBuilt-in

In practice the line is blurrier than the table suggests. Pulumi is declarative at the engine level (it computes a diff against state) even though you author in an imperative language; Ansible is declarative per-module but procedural per-play (tasks run in written order). The interview-safe framing: declarative tools own the desired state and the convergence logic; imperative tools execute steps and you own the convergence logic. Declarative wins for infrastructure because the desired state of a fleet changes far less often than the code that describes it, and a diff-able declaration is reviewable in a way a 300-line script never is.

Mutable vs Immutable Infrastructure

AspectMutableImmutable
UpdatesSSH + configure in placeReplace entire instance
ToolsAnsible, PuppetPacker + Terraform
DriftCommonImpossible
RollbackManualSwitch to old image

Mutable configuration management (patch in place) maximizes speed of small fixes and works well for stateful or long-lived systems (databases, bare metal). Immutable infrastructure (bake a golden image with Packer, then replace instances) maximizes consistency — every deploy is a fresh boot from a known artifact, so “that one weird box” cannot exist. Modern platform teams are usually hybrid: immutable for the compute fleet, mutable configuration for the stubborn remainder, all driven from the same Git repos.

State Management Concepts

Most IaC interview questions about “state” reduce to four ideas:

  • Desired state vs actual state — the declaration (in Git) is what you want; the live world is what you have. The gap between them is the work to be done.
  • The state store — tools like Terraform keep an extra artifact, a state file, mapping declared resources to real resource IDs. Ansible deliberately has none: it inspects live systems each run (stateless convergence). Kubernetes etcd is the state store for the cluster world.
  • Locking — two writers converging on the same estate corrupt or race each other; locking serializes applies (S3+DynamoDB, Pulumi’s backend, admission control in k8s).
  • Drift — actual state moving without a declaration changing. Detected by re-planning or by continuous reconciliation; repaired by re-apply or by adopting the change into code.

Which tools keep a store — and what they can therefore do — explains most cross-tool behavior questions:

Tool familyPersistent state store?How it computes convergence
Terraform / OpenTofu / PulumiYes — state file in a backendDiff config vs state vs live (plan)
AnsibleNo — inspects live hosts each runPer-module check-then-act
Kubernetes / CrossplaneYes — etcd, via declared objectsController reconciliation loop
Puppet / ChefServer inventory (partial)Agent pulls catalog, enforces it

Tools with a store can plan — show you the work before doing it — but must protect that store (locking, encryption, backups). Tools without one are simpler to reason about but pay discovery cost on every run and cannot show a diff before acting. This single trade-off is the deepest idea in the section and answers half of all “why does tool X behave differently” questions.

Push vs Pull Models

Push:  CI/operator --apply--> [tool holds creds] --> Cloud APIs / nodes
Pull:  node/agent --poll--> declared state (Git/registry) --> converge self
AspectPush (Terraform, Ansible, Pulumi)Pull (Puppet/Chef agents, k8s controllers, GitOps: Argo CD, Flux)
Initiated byCI or operator runs the toolNode or controller reconciles continuously
CredentialsTool holds broad cloud credentialsNodes hold narrow read-only credentials
Drift windowUntil the next apply (possibly forever)Bounded by the reconciliation interval
Natural homeProvisioning, one-shot change setsContinuous convergence, in-cluster config

Neither model wins outright: provisioning a VPC is inherently a push (there is no “VPC agent” to pull), while keeping 500 deployed manifests in sync is naturally a pull. That is exactly why mature organizations run both — Terraform pushing infrastructure, GitOps pulling workloads — as covered in GitOps.

A second differentiator is who holds credentials: push tools need broad cloud credentials wherever they run (a compromised CI runner is an incident), while pull agents hold narrow read-only credentials and act only on their own scope. When an interviewer asks “why is GitOps more secure?”, the honest answer is smaller credential blast radius plus bounded drift — not magic.

The Tool Landscape

ToolTypeLanguageApproach
TerraformProvisioningHCLDeclarative
AnsibleConfigurationYAMLProcedural
PulumiProvisioningAny languageImperative
CloudFormationProvisioningJSON/YAMLDeclarative
CDKProvisioningAny languageImperative → declarative

The table above is the classic starter set; the full 2020s landscape adds a second ring of tools that matter in interviews:

ToolNicheOne-liner
OpenTofuTerraform forkSame HCL-and-state model, Linux Foundation, open licence (opentofu.org)
PulumiDev-centric IaCReal programming languages, engine still diffs state (pulumi.com)
CDK / CDKTFApp-team IaCLanguages compile down to CloudFormation / Terraform
CrossplanePlatform IaCInfrastructure as Kubernetes CRDs, continuously reconciled
AnsibleConfig managementAgentless SSH, procedural plays, no state file
Cloud-initFirst bootThe bootstrap step before configuration management takes over
PackerImage buildingBakes the golden images immutable fleets boot from
            ┌──────────────────── lifecycle of a machine ────────────────────┐
            │                                                                 │
  Packer    │   Terraform / OpenTofu / Pulumi / Crossplane                    │   Ansible
  bakes     │ → provisions network + compute (declarative, stateful)       →  │   configures
  image     │   Cloud-init runs at first boot                               │   running hosts
            └─────────────────────────────────────────────────────────────────┘

The boundary question — “which tool where” — is a standard interview probe; the canonical answer is Terraform (or a sibling) for provisioning, Ansible for configuration, Packer for images, as detailed in Beyond Terraform.

Read the two tables together and the landscape organizes into four questions: provision vs configure (Terraform-family vs Ansible), DSL vs real language (HCL vs Pulumi/CDK), single cloud vs multi-cloud (CloudFormation/CDK vs Terraform-family), and push vs continuous reconcile (one-shot tools vs Crossplane/GitOps). Every tool in the landscape is a point in that 4-axis space, which is why “compare tool X and Y” questions are answerable even for tools you have never installed.

The IaC Maturity Ladder

LevelNameBehaviourSignature smell
0ManualConsole clicks, SSH“Works on prod only”; no rebuild possible
1ScriptedAd-hoc bash/scripts in a wikiScripts rot; only the author dares run them
2DeclarativeTerraform/Ansible code in GitState on a laptop; drift quietly ignored
3PipelinedCI plans on PR, applies on mainPlan posted to the PR; no human terminals
4GovernedPolicy-as-code gates, drift alertsOPA/Sentinel checks; module registry
5PlatformSelf-service golden pathsDevs order infra via portal/CRDs, never touch IaC

Interviews rarely name the ladder, but “where is your team on it and what is the next step” is exactly what a seniority probe is measuring. Moving from 2 to 3 (pipeline-only applies, humans lose terminal access to prod) is the transition most organizations are stuck in front of, because it is as much a process change as a tooling one. Levels 4–5 connect to Policy as Code and platform engineering.

When asked to place a team, look for the tell: if engineers can name which resources are manually managed, they are at level 2; if plan diffs appear on PRs, level 3; if a PR cannot merge because a policy denied an unencrypted volume, level 4. Describing your own position honestly — including the manually-managed exceptions everyone has — reads far better than claiming level 5.

How Interviews Test IaC

Expect five archetypes, roughly in seniority order:

  1. Definitional — declarative vs imperative, mutable vs immutable, push vs pull. Cheap; get them fluent.
  2. Mechanism — what a state file contains, why locking exists, how a plan is computed, what “idempotent” means for a module. See Terraform.
  3. Design — structure Terraform for three environments and forty services; module boundaries; workspaces vs directories. The graded part is trade-off articulation, not the layout you pick — say what each alternative costs.
  4. Debugging — lost/corrupt state, stuck lock, a provider upgrade breaking a plan, unexplained drift. These are story questions: name the diagnosis steps out loud.
  5. Judgment — Terraform vs Pulumi vs CDK vs Crossplane; when Ansible alone is fine; where policy-as-code belongs. The correct answer is a decision procedure, not a brand.

For the design archetype, a concrete prompt looks like: “Forty microservices, three environments, two clouds — lay out the repos, the state files, and the CI gates, and justify who can apply what.” Strong candidates sketch repo layout, state boundaries, and the review/policy gates as three separate diagrams-in-words before arguing any tool choice.

What IaC Does Not Solve

Honest answers about limits read as senior:

  • Runtime behavior — IaC provisions and configures; it does not make an application resilient. Availability comes from architecture (retries, failover, capacity), and a perfectly IaC-managed stack can still fall over.
  • Secrets lifecycle by itself — IaC can create vaults and IAM bindings, but rotating the values inside is a separate mechanism (rotation policies, lambdas, vault tooling).
  • Cost discipline — declarative config makes spend visible (tagged, reviewable), but waste needs FinOps practice: rightsizing, budgets, lifecycle rules.
  • Institutional knowledge — code documents what exists, not why; ADRs and runbooks still carry the reasoning that a resource block cannot.

Behavioral mirror: “tell me about a time infrastructure code saved (or burned) you” — have a drift or state story ready. The consolidated bank is in IaC Interview Questions.

Three Stories to Have Ready

Scenario questions are answered best from a rehearsed skeleton, not improv:

  • The drift story — a manual console change, how a plan surfaced it, whether you adopted or reverted it, and the access fix that prevented the next one (arc: detect → decide intent → reconcile → close the hole).
  • The state incident — a corrupt, locked, or lost state file; emphasize that resources were never destroyed, recovery ran through backend versioning or imports, and the fix was versioned locked backends (arc: restore → rebuild → prevent).
  • The migration story — introducing IaC (or a second tool) into a live estate: one subsystem first, import existing resources, dual-run briefly, dated decommission plan (arc: increment → adopt → converge → decommission).

Each skeleton has four beats and takes under two minutes to tell; practice all three until the beats are automatic.

Cross-References

  • Terraform — HCL, state, plan/apply lifecycle, modules, footguns.
  • Ansible — inventories, playbooks, roles, idempotency model, performance.
  • Beyond Terraform — OpenTofu fork, Pulumi, CDK, Crossplane, Terragrunt decision tree.
  • IaC Interview Questions — 18-question graded bank.
  • GitOps — the pull-model endgame for workloads.
  • Policy as Code — OPA, Kyverno, Sentinel guarding IaC outputs.
  • Packer — image baking, the piece IaC provisioning deliberately does not own.

Interview Questions

Q: What problem does IaC actually solve — in one sentence each for drift, repeatability, and review? A: Drift: declared config becomes the source of truth, so divergence is a visible diff instead of an archaeology project. Repeatability: any environment becomes a re-run of a reviewed pipeline instead of a human rebuild. Review: infrastructure changes flow through pull requests with diffs, reviewers, and CI gates, like any other code.

Q: Is Ansible declarative or imperative? And Pulumi? A: Ansible is procedural overall (tasks execute in written order) but each module is declarative (it asserts a desired end state), so plays converge when modules are idempotent. Pulumi is authored imperatively in a real language but is declarative at the engine level: your program registers desired resources, and the engine diffs against state like Terraform. The safe answer is to name both layers — authoring model vs execution model.

Q: Why do teams combine Terraform and Ansible instead of picking one? A: They occupy different lifecycle stages: Terraform provisions the estate (networks, compute, managed services) with a persistent state file, while Ansible configures what runs on the hosts, needing no state store because it inspects live systems. Terraform deliberately avoids in-place guest configuration; Ansible deliberately avoids cloud resource graphs. The seam is real but manageable — and increasingly the configuration half is replaced by baked images and Kubernetes.

Q: Your org is at maturity level 2 (IaC in Git, but humans apply from laptops). What is the next move and why is it hard? A: Move to level 3: plan on every PR, apply only from CI on merge, and revoke human write access to production state. It is hard because it is organizational, not technical — on-call engineers lose the “just fix it in the console/terminal” escape hatch, so break-glass procedures, drift alerting, and pipeline reliability must exist before the access cut. Stage it subsystem by subsystem rather than as a big-bang lockdown.

References