Engineering Notes
Home/DevOps & Cloud/Terraform/Infrastructure as Code Fundamentals
DevOps & Cloud · Terraform

Infrastructure as Code Fundamentals

Clicking around a cloud console doesn't scale, can't be reviewed and can't be reproduced. Infrastructure as Code describes infrastructure in versioned files that an engine reconciles with reality. See the reconcile loop, compare declarative and imperative approaches, and meet the tool landscape.

☁️ DevOps & Cloud● Beginner⏱ 5 min read▶ 2 interactive🛠️ 2 exercises🎤 5 interview Q&As

🎯 What you'll learn

  • Why ClickOps fails: drift, no review, no reproducibility
  • Declarative vs imperative infrastructure
  • The tool landscape: Terraform, CloudFormation/CDK, Pulumi, Ansible
  • Core principles and GitOps

📚 Before you start

1The Problem: ClickOps

Creating infrastructure by clicking through a cloud console works once. Then:

  • Nobody remembers exactly which settings were chosen ("why is this security group open to 0.0.0.0/0?").
  • Staging and production drift apart, so bugs appear only in production.
  • Rebuilding after a disaster (or in a new region) takes days of guesswork.
  • Changes have no review, no history, no rollback.

Infrastructure as Code (IaC) describes infrastructure in versioned text files that a tool turns into real resources. The same review, history, testing and automation you use for application code now applies to networks, databases, DNS, IAM and clusters.

2Declarative vs Imperative

Imperative (scripts)Declarative (desired state)
you writethe steps: "create VPC, then subnet, then…"the end state: "there is a VPC with these 3 subnets"
re-runningmay fail or duplicate ("VPC already exists")idempotent: no changes if reality matches
changesyou script the transitionthe tool computes the diff (plan)
examplesbash + AWS CLI, Ansible playbooks (mostly), SDK scriptsTerraform/OpenTofu, CloudFormation, Pulumi*, Kubernetes manifests

\* Pulumi and AWS CDK use general-purpose languages but still produce a declarative model that an engine reconciles.

hcl
# Declarative: "this bucket exists with versioning on"
resource "aws_s3_bucket" "assets" {
  bucket = "acme-prod-assets"
}
resource "aws_s3_bucket_versioning" "assets" {
  bucket = aws_s3_bucket.assets.id
  versioning_configuration { status = "Enabled" }
}
Imperative script
bash
aws s3api create-bucket --bucket petpal-uploads
aws s3api put-bucket-versioning --bucket petpal-uploads \
  --versioning-configuration Status=Enabled
# run it twice → BucketAlreadyExists error
# someone disables versioning → script doesn't notice
Declarative config
hcl
resource "aws_s3_bucket" "uploads" {
  bucket = "petpal-uploads"
}
resource "aws_s3_bucket_versioning" "uploads" {
  bucket = aws_s3_bucket.uploads.id
  versioning_configuration { status = "Enabled" }
}
# apply twice → "No changes"; drift → plan shows the fix

3The IaC Tool Landscape

ToolScopeLanguageState
Terraform / OpenTofuany cloud/SaaS with a provider (AWS, GCP, Azure, Cloudflare, GitHub, Datadog…)HCLstate file you manage (remote backend)
AWS CloudFormationAWS onlyYAML/JSONmanaged by AWS (stacks)
AWS CDKAWS (synthesizes CloudFormation)TS, Python, Java, C#, GoCloudFormation stacks
Pulumimulti-cloudTS, Python, Go, C#, JavaPulumi service or self-managed backend
Ansible / Chef / Puppetconfiguring servers (packages, files, services)YAML / Ruby DSLmostly stateless
Kubernetes manifests + Helm/Kustomizeresources inside a clusterYAMLthe cluster itself (devops-cloud/kubernetes/)

Rule of thumb: provisioning (create cloud resources) → Terraform/CloudFormation/CDK/Pulumi; configuration of long-lived servers → Ansible (increasingly replaced by immutable images + containers).

4Core Principles

  1. Everything in version control — infrastructure changes go through pull requests with review.
  2. Plan before apply — always look at the computed diff; destructive changes must be visible.
  3. Idempotency — running the same code twice changes nothing the second time.
  4. Immutable infrastructure — replace servers/images instead of patching them in place; snowflake servers disappear.
  5. Environments from the same code — dev/staging/prod differ only in variables (06-workspaces_and_environments/).
  6. No manual changes in production — manual edits create drift between code and reality; detect and reconcile it (07-terraform_in_ci_cd/).
  7. Small blast radius — split infrastructure into independently applied units (network, data, apps) so one mistake can't take down everything.
  8. Secrets are not in code — reference a secrets manager; state files may contain secrets and must be protected (03-state_and_remote_backends/).

5GitOps

GitOps extends IaC: Git is the single source of truth, and an automated agent continuously reconciles the live system to match the repository (pull-based: Argo CD/Flux for Kubernetes; push-based: CI running terraform apply on merge). Rolling back = reverting a commit.

Under the HoodHow a Declarative Engine Works▾

Every declarative IaC tool runs the same loop:

  1. Parse the configuration into a set of desired resources with attributes.
  2. Build a dependency graph from references between resources (a subnet references a VPC's ID → the VPC must exist first). Independent resources can be created in parallel.
  3. Read the current state — what the tool believes exists (Terraform's state file / CloudFormation's stack) — and refresh it by asking the provider's API what really exists.
  4. Diff desired vs current, per resource: create, update in place, replace (destroy + create, when an immutable attribute changes, e.g. an RDS instance's engine), destroy, or no-op.
  5. Apply the diff in dependency order (reverse order for destroys), recording results in state as each operation completes.

That's why the same code is safe to re-run, why a plan can show "must be replaced" warnings, and why losing or corrupting the state file is serious: without it the tool no longer knows which real resources it owns.

ClickOps produces drift and snowflakes; with IaC, code in Git is reviewed, planned against current state, and applied by an engine that builds a dependency graph, diffs desired versus actual, and creates, updates or replaces resources
ClickOps produces drift and snowflakes; with IaC, code in Git is reviewed, planned against current state, and applied by an engine that builds a dependency graph, diffs desired versus actual, and creates, updates or replaces resources

⚠️ Common Pitfalls

  1. Mixing manual console changes with IaC — drift and surprise destroys on the next apply.
  2. One giant configuration for everything — slow plans, huge blast radius.
  3. Applying without reading the plan — "replace" on a database means data loss.
  4. Secrets hard-coded in IaC files or unprotected state.
  5. Copy-pasting environments instead of parameterizing one codebase.
  6. Treating IaC as "write once" — it needs reviews, tests and upgrades like any code.

📋 Quick Reference

text
IaC = infrastructure described in versioned code, applied by a tool
declarative: describe end state → tool computes plan → idempotent applies
principles: VCS + review · plan before apply · immutable · same code per env · no manual changes · small blast radius · no secrets in code
tools: Terraform/OpenTofu (multi-cloud) · CloudFormation/CDK (AWS) · Pulumi · Ansible (config) · K8s manifests

🧠 Key takeaways

  • ClickOps can't be reviewed, reproduced or audited, and it drifts.
  • Declarative IaC states what should exist; the engine computes how (idempotent, drift-correcting).
  • Engines diff desired vs state vs reality, show a plan, then apply and record state.
  • Principles: everything in Git, review every change, small blast radius, GitOps automation.

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1What are the benefits of infrastructure as code?core▾

Reproducible environments, version control and code review for infrastructure, automation, drift detection, documentation as code, and faster disaster recovery.

Follow-ups interviewers ask
  • What's configuration drift? Manual changes making real infrastructure differ from code.
Q2Declarative vs imperative IaC?core▾

Declarative (Terraform, CloudFormation, Kubernetes): describe the desired end state; the tool computes changes. Imperative (scripts, some SDK approaches): describe the steps. Declarative is idempotent and easier to reason about.

Follow-ups interviewers ask
  • Where do Pulumi/CDK fit? General-purpose languages generating declarative state.
Q3Provisioning vs configuration management?senior▾

Provisioning tools (Terraform) create infrastructure resources; configuration management tools (Ansible, Chef) configure software on servers. With immutable images and containers, configuration management matters less.

Follow-ups interviewers ask
  • What's immutable infrastructure? Replace servers with new images instead of modifying them.
Q4How do you introduce IaC to an existing environment?core▾

Import existing resources (terraform import or import blocks), start with new resources and low-risk areas, generate configuration, reconcile drift, and enforce changes only through pipelines.

Follow-ups interviewers ask
  • Tooling to generate code? terraform -generate-config-out, Terraformer, former2.
Q5How do you prevent manual changes?core▾

Restrict console write access, apply changes only through CI/CD with reviews, detect drift with scheduled plans, and use policies (SCPs) to block changes outside pipelines.

Follow-ups interviewers ask
  • What's break-glass access? Audited emergency access for incidents.

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.

✅ Check yourself

Answer the questions — feedback is instant.

1. Running a declarative config twice with no changes results in…
Idempotent by design.
2. A setting was changed in the console. What does the next plan show?
Drift is detected by refresh.
3. Which tool is primarily configuration management of servers rather than provisioning?
Ansible configures hosts imperatively-ish.
4. What is the source of truth in IaC?
Review and history come from Git.