Skip to content
Infrastructure as Code iac getting-started 4 min read

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:

StyleHow you express intentExample tools
ImperativeStep-by-step instructions (“create X, then Y”)Shell scripts, Ansible (procedural)
DeclarativeDesired end state; the engine computes the stepsTerraform, 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 terraform for tofu and 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.
  • Automationterraform apply runs 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 detectionterraform plan surfaces 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 apply may 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 plan and review the diff before terraform 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.
Last updated June 14, 2026
Was this helpful?