What is Infrastructure as Code?
Infrastructure as Code (IaC) is the practice of defining and managing your infrastructure — servers, networks, databases, DNS records, load balancers, and more — using machine-readable configuration files instead of manual point-and-click work in a cloud console. Those files live in version control alongside your application code, get reviewed in pull requests, and are applied by automation. The result is infrastructure that is repeatable, auditable, and consistent across every environment. This curriculum is Terraform-centric, using modern HashiCorp Configuration Language (HCL2) and Terraform 1.5+.
The problem IaC solves
Provisioning infrastructure by hand does not scale. Clicking through a web console to create a virtual machine, attach a security group, and wire up a database might work once, but reproducing it exactly for staging, production, and a teammate’s sandbox is error-prone. Manual changes drift over time, nobody remembers who changed what, and disaster recovery becomes guesswork.
IaC replaces that workflow with code. You describe the desired state of your infrastructure once, and the tool figures out how to reach it — creating, updating, or destroying resources as needed.
Declarative vs. imperative
There are two broad styles of infrastructure automation:
| Style | How you express intent | Example tools |
|---|---|---|
| Imperative | Step-by-step instructions (“create X, then Y”) | Shell scripts, Ansible (procedural) |
| Declarative | Desired end state; the engine computes the steps | Terraform, OpenTofu, CloudFormation |
Terraform is declarative. You write what you want, not how to build it. The engine compares your configuration against the real world and produces an execution plan to converge them.
A tiny Terraform configuration
Here is a minimal, real configuration that provisions an S3 bucket on AWS. It declares the required provider, configures a region, and defines a single resource.
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "assets" {
bucket = "devcraftly-static-assets"
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
You apply it with two commands:
terraform init
terraform apply
Output:
Terraform will perform the following actions:
# aws_s3_bucket.assets will be created
+ resource "aws_s3_bucket" "assets" {
+ bucket = "devcraftly-static-assets"
+ id = (known after apply)
+ tags = {
+ "Environment" = "production"
+ "ManagedBy" = "terraform"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
aws_s3_bucket.assets: Creating...
aws_s3_bucket.assets: Creation complete after 2s [id=devcraftly-static-assets]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Run the same apply again and Terraform reports 0 to add — the desired state already matches reality. That idempotency is the heart of IaC.
Tip: Everything you write in Terraform also runs on OpenTofu, the open-source, community-governed fork. The CLI is a drop-in replacement — swap
terraformfortofuand the configuration above works unchanged.
Why teams adopt IaC
- Repeatability — spin up identical staging and production environments from the same code.
- Version control — every change is a Git commit with history, blame, and the ability to roll back.
- Code review — infrastructure changes go through pull requests, just like application changes.
- Automation —
terraform applyruns in CI/CD pipelines, removing manual gatekeeping. - Documentation — the configuration is the source of truth; there is no stale wiki describing what should exist.
- Drift detection —
terraform plansurfaces when reality has diverged from your code.
Where IaC fits in DevOps
IaC is a foundational DevOps practice. It bridges the gap between developers and operations by making infrastructure a shared, reviewable artifact. In a typical pipeline, a commit triggers tests, builds an artifact, and then a Terraform stage provisions or updates the environment that artifact deploys into. Because the infrastructure definition lives next to the application, both evolve together and are promoted through environments as a unit.
Terraform also keeps a state file that records the mapping between your configuration and real-world resources. This state is what lets Terraform compute precise diffs and is central to how teams collaborate safely on shared infrastructure.
Warning: Never edit cloud resources by hand once they are managed by Terraform. Out-of-band changes cause drift, and the next
applymay revert or conflict with your manual edits. Make every change through code.
Best Practices
- Store all Terraform configuration in version control from day one — never start in the console.
- Keep environments (dev, staging, prod) consistent by sharing modules and varying only inputs.
- Always run
terraform planand review the diff beforeterraform apply. - Use remote state with locking (for example, an S3 backend with DynamoDB locking) for any shared work.
- Pin provider and Terraform versions so builds are reproducible across machines and CI.
- Tag resources consistently (owner, environment, cost center) so they remain discoverable and auditable.