Infrastructure as Code
Infrastructure as code (IaC) means defining servers, networks, databases, DNS records, IAM policies, and every other piece of infrastructure in machine-readable files that live in version control. Tools read those files and create or update real resources to match. Instead of clicking through cloud consoles, you open a pull request, review a plan of exactly what will change, and apply it through automation.
IaC turns infrastructure into something you can review, test, reproduce, and roll back. It's the foundation for consistent environments, disaster recovery (rebuild from code), audit trails (who changed what, when, and why), and platform engineering. It also introduces its own discipline: state management, drift, module design, and safe change workflows.
TL;DR
- Keep all infrastructure definitions in Git; changes happen through pull requests.
- Most tools are declarative: describe the end state; the tool computes the diff.
- State maps code to real resources — store it remotely, lock it, and protect it.
- Always run plan before apply, ideally in CI with the plan posted to the PR.
- Build reusable modules; separate environments with distinct state and credentials.
- Add policy as code and drift detection; never make untracked manual changes.
Quick Example
A pull-request workflow for Terraform/OpenTofu that plans on every PR and applies on merge:
A typical repository layout separating reusable modules from environment roots:
Core Concepts
Declarative vs Imperative
- Declarative — describe what should exist ("a bucket named X with encryption"); the tool figures out how. Terraform, OpenTofu, CloudFormation, Pulumi (declarative engine, imperative language), Kubernetes manifests.
- Imperative — describe steps to perform. Shell scripts, SDK scripts, some Ansible usage. Harder to make idempotent and to preview.
Provisioning vs Configuration
State
Most declarative tools keep state: a record mapping code to real resource IDs and attributes. State enables diffs and dependency tracking but is sensitive (it can contain secrets) and must not be edited concurrently. Store it in a remote backend (S3 with locking, GCS, Azure Blob, HCP Terraform, Pulumi Cloud), encrypt it, and restrict access.
Plan, Apply, and Drift
A plan compares code, state, and reality, then shows creates, updates, and destroys. Apply executes it. Drift occurs when reality changes outside the tool (manual console edits, other automation). Scheduled plans or dedicated drift detection highlight it so you can reconcile.
Modules and Composition
Modules package resources behind inputs and outputs — a "service" module might create a container service, load balancer, DNS record, and alarms. Good modules encode organizational standards (encryption, tagging, logging) so teams get them by default. Version modules and pin versions in callers.
Testing and Policy as Code
- Static checks — formatting, validation, linters (
tflint), security scanners (Checkov, Trivy). - Policy as code — OPA/Conftest, Sentinel, or Pulumi CrossGuard enforce rules on plans.
- Unit and integration tests —
terraform test, Terratest, or Pulumi tests deploy ephemeral environments.
Best Practices
Everything Through Pull Requests
Review code and the plan output together. Require approvals for production changes.
Separate State per Environment and Component
Split state by environment and by blast radius (network, data, applications) so a mistake can't destroy everything at once and plans stay fast.
Use Short-Lived CI Credentials
Authenticate pipelines with OIDC to assume narrowly scoped roles; no long-lived cloud keys in CI secrets.
Keep Secrets Out of Code
Reference secrets from a secret manager rather than hard-coding them, and treat state as sensitive. See Secrets Management.
Protect Stateful Resources
Use deletion protection, prevent_destroy lifecycle rules, and backups for databases and buckets.
Tag Everything
Enforce owner, environment, and cost-center tags in modules and policy so resources are attributable. See Cloud Costs.
Common Mistakes
Applying From Laptops
Local applies with personal credentials bypass review and create untraceable changes. Apply from CI.
One Giant State File
Every plan touches everything, takes minutes, and a single error blocks all teams.
Console "Quick Fixes"
Manual changes drift from code and get reverted or cause conflicts on the next apply.
Ignoring Destroy and Replace in Plans
Renaming a resource or changing an immutable property can replace a production database. Read plans carefully and use moved blocks or aliases when refactoring.
Copy-Pasted Environments
Duplicated code per environment drifts apart. Use modules with environment-specific inputs.
Comparison
FAQ
What is infrastructure as code?
Managing infrastructure through versioned code files that tools apply automatically, instead of manual console changes — making infrastructure reviewable, repeatable, and auditable.
What's the difference between declarative and imperative IaC?
Declarative IaC describes the desired end state and lets the tool determine the changes. Imperative IaC lists steps to execute. Declarative tools are easier to preview and make idempotent.
Why does Terraform need a state file?
State maps resources in code to real-world resources and stores their attributes, so the tool can compute accurate diffs, track dependencies, and manage resources it created.
How do I handle drift?
Detect it with scheduled plans or drift detection features, then either update the code to accept the change or re-apply to revert it. Prevent it by restricting manual write access.
Should I use Terraform or Pulumi?
Terraform/OpenTofu has the largest ecosystem and a simple declarative language. Pulumi suits teams that want general-purpose languages, stronger abstractions, and unit testing. Both work well.
Related Topics
- Terraform — The most widely used IaC tool
- Pulumi — IaC in general-purpose languages
- CloudFormation — AWS-native IaC
- Ansible — Configuration management
- GitOps — Git-driven reconciliation for Kubernetes