Infrastructure as Code & Terraform
Infrastructure as Code & Terraform
Before Terraform, provisioning a cloud environment meant clicking through a web console, running ad-hoc AWS CLI commands, or writing fragile shell scripts that worked once and then became tribal knowledge no one dared touch. The result was snowflake infrastructure — every environment subtly different, impossible to reproduce reliably, and terrifying to change.
Infrastructure as Code (IaC) is the practice of describing your infrastructure in version-controlled, human-readable files and letting a tool provision and manage it from that description. At Google, Meta, and every serious cloud-native shop, no production resource is created by clicking. Everything — VPCs, IAM roles, EKS clusters, RDS instances, DNS records — is declared in code, reviewed via pull request, and applied through an automated pipeline. The audit trail is in Git; the blast radius of any change is visible before it lands.
Declarative vs Imperative IaC
There are two approaches to IaC: imperative and declarative. Ansible playbooks and shell scripts are imperative — you specify the steps to reach a desired state. Terraform is declarative — you specify what the end state should look like and let Terraform figure out the steps.
The declarative model has a decisive advantage at scale: idempotency. Running terraform apply ten times against the same config produces the same infrastructure every time. No side-effects, no cumulative drift. With an imperative script, running it twice might create duplicate resources, fail on already-existing objects, or leave the environment half-configured.
The Plan / Apply Loop
Terraform's core workflow is a three-phase loop: Write → Plan → Apply. This is the discipline that separates professional IaC from script-and-pray.
Write: you author .tf files describing the resources you want. Terraform uses HCL (HashiCorp Configuration Language), a configuration language designed to be readable by both humans and machines.
Plan: terraform plan computes the difference between your declared configuration and the current real-world state (tracked in a state file). It prints a precise diff — which resources will be created, changed, or destroyed — without touching anything. This is your pre-flight check. In production CI pipelines, the plan output is posted as a PR comment so reviewers can approve the exact changes before they land.
Apply: terraform apply executes the plan. Terraform calls provider APIs in dependency order, creating resources in parallel where possible, and updates the state file to record what now exists.
terraform plan before apply, even locally. In production, make the plan artifact mandatory: generate it with terraform plan -out=tfplan, store it as a CI artifact, then apply that exact plan with terraform apply tfplan. This guarantees what was reviewed is what gets applied — no race conditions where the environment changes between plan and apply.
Providers: Terraform's Extension Model
Terraform itself has no knowledge of AWS, Kubernetes, or Cloudflare. All resource types live in providers — plugins that translate Terraform's declarative resource definitions into real API calls. The AWS provider makes calls to the AWS APIs; the Kubernetes provider talks to the cluster's API server; the GitHub provider manages repositories and team memberships.
A provider block in your config declares which provider to use and at which version. Terraform downloads providers from the Terraform Registry during terraform init and caches them locally in .terraform/.
The ~> 5.0 version constraint is a production-critical pattern. It pins the major version, allowing automatic patch and minor updates (5.1, 5.2...) while blocking 6.0 which may have breaking changes. Omitting version constraints causes terraform init to download whatever is latest — a silent way to import breaking changes into your infrastructure codebase.
How Terraform Knows What Exists: The State File
Terraform tracks every resource it manages in a state file (terraform.tfstate). The state file is the bridge between your HCL configuration and real-world infrastructure. When you run terraform plan, Terraform reads both the config and the state, calls the provider to refresh live resource attributes, and computes the diff.
This has an important consequence: resources created outside Terraform (by clicking in the console, or by another team's script) are invisible to Terraform unless you explicitly import them. This is both a feature (clear ownership) and a common trap (engineers who bypass Terraform create untracked resources that later cause confusing plan diffs or orphaned costs).
terraform.tfstate to Git, and never store it locally for shared infrastructure. The state file contains sensitive data — resource IDs, IP addresses, and sometimes secrets output from resources. More critically, if two engineers apply against the same local state file simultaneously, the state becomes corrupted and your infrastructure is in an unknown condition. The solution is a remote backend (S3 + DynamoDB for locking, or Terraform Cloud) — covered in Lesson 6. On every real team, day one of the Terraform setup is configuring the remote backend. Local state is only acceptable for personal experiments.
Your First Terraform Workflow End-to-End
Here is a minimal but complete example that creates an AWS S3 bucket and outputs its ARN. Run this against a personal AWS account to get the muscle memory for the workflow:
Notice that aws_s3_bucket.example.arn references the bucket's ARN attribute before the resource exists. Terraform resolves these references at apply time, building a dependency graph to determine execution order. This reference syntax — resource_type.name.attribute — is fundamental to everything in Terraform and is covered in depth in Lesson 7.
terraform plan time instead of in the editor, which is a slower feedback loop.
The next lesson dives into HCL syntax in depth — blocks, arguments, expressions, type system, and how to write your first real multi-resource configuration. By Lesson 10 you will have provisioned a full web stack — load balancer, EC2 instances, RDS, and Route 53 — entirely from Terraform code.