# Infrastructure as Code Explained
For years, provisioning infrastructure meant clicking through a cloud console: create a VPC, click, add a subnet, click, attach a security group, click. It worked until you needed to do it again, consistently, in three environments, and remember exactly what you did six months ago. Infrastructure as Code (IaC) replaces that clicking with version-controlled definitions you can review, repeat, and audit.
What IaC actually is
IaC means defining your infrastructure — networks, servers, databases, load balancers, DNS — in machine-readable files that a tool reads to create and manage real resources. Instead of manual steps, you write the desired state and let the tool make reality match.
# Terraform: a desired-state definition
resource "aws_s3_bucket" "assets" {
bucket = "my-app-assets"
}
resource "aws_s3_bucket_versioning" "assets" {
bucket = aws_s3_bucket.assets.id
versioning_configuration { status = "Enabled" }
}That file *is* your infrastructure. It lives in Git, gets code-reviewed, and produces the same result every time.
Why it matters
- Repeatability. Stand up an identical environment (dev/staging/prod) from the same code. No "it works in staging but prod is configured differently."
- Version control. Every change is a commit — reviewable, attributable, revertable. You can answer "who changed the firewall rule and when."
- Disaster recovery. If a region or account is lost, you re-apply the code instead of rebuilding from memory.
- Documentation. The code *is* the source of truth. No stale wiki describing a setup that drifted months ago.
- Collaboration. Infrastructure changes go through PRs like application code.
Declarative vs imperative
A fundamental split in IaC tools:
| Declarative | Imperative | |
|---|---|---|
| You specify | *What* you want (end state) | *How* to get there (steps) |
| Tool figures out | The steps to reach it | Nothing — you do |
| Idempotent | Naturally | You must ensure it |
| Examples | Terraform, CloudFormation, Pulumi (mostly) | Shell scripts, some Ansible usage |
Declarative dominates modern IaC. You describe the desired end state; the tool computes the diff between current and desired and applies only what's needed. This makes operations idempotent — running the same config twice doesn't create duplicates, it converges on the desired state.
The plan/apply workflow
Good IaC tools separate *previewing* changes from *making* them. With Terraform:
terraform plan # show what WILL change — review before applying
terraform apply # actually make the changesThe plan step is your safety net: you see exactly which resources will be created, modified, or destroyed before anything happens. "Plan shows it's destroying the database" is a sentence that has saved many engineers.
State: the part people underestimate
Declarative tools track what they've created in a state file that maps your config to real-world resources. This is both powerful and a common source of pain:
- Store state remotely (S3, GCS, Terraform Cloud) — never just on a laptop.
- Lock state during applies so two people don't corrupt it concurrently.
- Protect state — it can contain secrets. Encrypt it and restrict access.
State drift (someone clicks in the console and reality diverges from state) is the enemy. The discipline is: *all changes go through code*, never the console.
The landscape of tools
- Terraform / OpenTofu — declarative, cloud-agnostic, HCL syntax; the most widely used. [OpenTofu](https://opentofu.org/) is an open-source fork.
- Pulumi — IaC in real programming languages (TypeScript, Python, Go) instead of a DSL.
- AWS CloudFormation / CDK — AWS-native; CDK lets you use real languages.
- Ansible — configuration management and provisioning, more procedural.
- Kubernetes manifests / Helm — declarative IaC *for what runs on a cluster* (a related layer).
Best practices
- Keep it in version control and require PR review for changes.
- Modularize. Reusable modules for common patterns (a standard service, a standard VPC) reduce copy-paste drift.
- Separate environments with workspaces or directories; never share state across prod and dev.
- Run it in CI/CD. Apply from a pipeline, not laptops, so changes are gated and logged.
- Never edit infrastructure by hand. Manual changes cause drift; if you must, import them back into code.
- Manage secrets properly — reference a secret store, don't hardcode credentials in
.tffiles.
Where IaC meets PaaS
IaC is essential when you're managing raw cloud primitives. But there's a spectrum: the more your platform abstracts, the less low-level IaC you write. A managed platform like PandaStack handles the underlying infrastructure — multi-region GKE, Kong ingress, Cloudflare DNS, managed databases via KubeBlocks — so you don't author Terraform for clusters and networking yourself. Your configuration becomes the higher-level stuff: services, env vars, domains, scaling, all driven from a Git-connected repo. For teams that *do* run their own clouds, IaC remains indispensable; for teams that want to ship product, abstracting it away is often the better trade.
References
- [Terraform documentation (HashiCorp)](https://developer.hashicorp.com/terraform/docs)
- [OpenTofu](https://opentofu.org/)
- [Pulumi documentation](https://www.pulumi.com/docs/)
- [AWS — Infrastructure as Code](https://aws.amazon.com/what-is/iac/)
- [The Twelve-Factor App](https://12factor.net/)
Want the benefits of repeatable infrastructure without writing Terraform for clusters? PandaStack handles the platform layer — you connect a repo. [Start at dashboard.pandastack.io](https://dashboard.pandastack.io).