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.
🎯 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 write | the steps: "create VPC, then subnet, then…" | the end state: "there is a VPC with these 3 subnets" |
| re-running | may fail or duplicate ("VPC already exists") | idempotent: no changes if reality matches |
| changes | you script the transition | the tool computes the diff (plan) |
| examples | bash + AWS CLI, Ansible playbooks (mostly), SDK scripts | Terraform/OpenTofu, CloudFormation, Pulumi*, Kubernetes manifests |
\* Pulumi and AWS CDK use general-purpose languages but still produce a declarative model that an engine reconciles.
# 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
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 noticeDeclarative config
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 fix3The IaC Tool Landscape
| Tool | Scope | Language | State |
|---|---|---|---|
| Terraform / OpenTofu | any cloud/SaaS with a provider (AWS, GCP, Azure, Cloudflare, GitHub, Datadog…) | HCL | state file you manage (remote backend) |
| AWS CloudFormation | AWS only | YAML/JSON | managed by AWS (stacks) |
| AWS CDK | AWS (synthesizes CloudFormation) | TS, Python, Java, C#, Go | CloudFormation stacks |
| Pulumi | multi-cloud | TS, Python, Go, C#, Java | Pulumi service or self-managed backend |
| Ansible / Chef / Puppet | configuring servers (packages, files, services) | YAML / Ruby DSL | mostly stateless |
| Kubernetes manifests + Helm/Kustomize | resources inside a cluster | YAML | the 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
- Everything in version control — infrastructure changes go through pull requests with review.
- Plan before apply — always look at the computed diff; destructive changes must be visible.
- Idempotency — running the same code twice changes nothing the second time.
- Immutable infrastructure — replace servers/images instead of patching them in place; snowflake servers disappear.
- Environments from the same code — dev/staging/prod differ only in variables (
06-workspaces_and_environments/). - No manual changes in production — manual edits create drift between code and reality; detect and reconcile it (
07-terraform_in_ci_cd/). - Small blast radius — split infrastructure into independently applied units (network, data, apps) so one mistake can't take down everything.
- 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:
- Parse the configuration into a set of desired resources with attributes.
- 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.
- 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.
- 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.
- 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.
⚠️ Common Pitfalls
- Mixing manual console changes with IaC — drift and surprise destroys on the next apply.
- One giant configuration for everything — slow plans, huge blast radius.
- Applying without reading the plan — "replace" on a database means data loss.
- Secrets hard-coded in IaC files or unprotected state.
- Copy-pasting environments instead of parameterizing one codebase.
- Treating IaC as "write once" — it needs reviews, tests and upgrades like any code.
📋 Quick Reference
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
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.
- 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.
- 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.
- 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.
- 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.
- 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.