Cloud Tech by Victor
DevOpsIntermediate

Terraform Basics

How Terraform's declarative state model, providers, and plan/apply workflow let infrastructure be versioned and reviewed like code, and the state pitfalls that trip up most teams.

Updated 2026-07-225 min read

Overview

Terraform lets you describe infrastructure, servers, networks, DNS records, databases, as declarative configuration files instead of manual console clicks or imperative scripts. You write what you want to exist; Terraform figures out what needs to be created, changed, or destroyed to get there, by comparing your configuration against a state file that records what it created last time. Because that configuration is just text, it gets the same benefits as application code: version control, code review, and repeatable, auditable changes. The trade-off is a genuinely new failure mode application developers don't usually deal with, state drift, where the real infrastructure and Terraform's record of it disagree.

Quick Reference

ConceptWhat it isCommand
ProviderPlugin that talks to a specific API (AWS, Azure, GCP, Cloudflare...)Declared in a required_providers block
ResourceA single infrastructure object Terraform managesresource "type" "name" { ... }
StateTerraform's record of what it created, mapped to real object IDsterraform show, terraform state list
PlanA dry-run diff between config and stateterraform plan
ApplyExecutes a plan against real infrastructureterraform apply

The commands below are grouped by workflow stage, not an exhaustive reference (see the official Terraform CLI docs for every flag), but the set that covers nearly all day-to-day Terraform work.

CommandDescriptionCopy
terraform initInitialize a working directory: download providers/modules and configure the backend.
terraform fmtRewrite configuration files to the canonical style.
terraform validateCheck configuration for syntax and internal consistency errors, without touching real infrastructure.
terraform versionShow the Terraform and provider versions in use.

Syntax

hcl
terraform {
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
  backend "s3" {
    bucket = "my-tfstate"
    key    = "prod/network.tfstate"
    region = "us-east-1"
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "web" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"

  tags = {
    Name = "web-server"
  }
}

Examples

bash
# Standard workflow - review before anything changes
terraform init      # downloads providers, configures the backend
terraform plan       # shows the diff; nothing is changed yet
terraform apply
hcl
# Variables make configuration reusable across environments
variable "instance_type" {
  type    = string
  default = "t3.micro"
}

resource "aws_instance" "web" {
  ami           = "ami-0abcdef1234567890"
  instance_type = var.instance_type
}

terraform.tfstate contains secrets in plaintext

State files often include sensitive values (database passwords, private keys) resolved directly from your configuration. Never commit a local terraform.tfstate to version control, use a remote backend with encryption at rest instead.

Visual Diagram

Common Mistakes

  • Committing terraform.tfstate to Git; it often contains plaintext secrets and immediately causes merge conflicts between teammates.
  • Running terraform apply directly without reviewing terraform plan's output first, especially in CI where a bad diff can destroy production resources unattended.
  • Manually editing or deleting a cloud resource that Terraform manages, then being surprised when the next plan shows it wants to "recreate" something that already exists, Terraform only knows what's in its state.
  • Hardcoding values (regions, AMI ids, account numbers) instead of using variables, making the same configuration impossible to reuse across environments.

Performance

  • terraform plan and apply runtime scales with the number of resources and how many depend on each other, Terraform parallelizes independent resources automatically, but long dependency chains serialize.
  • Remote state with locking adds a small network round-trip per operation but is non-negotiable for team safety; the alternative (local state) doesn't scale past one person.
  • Large monolithic configurations (hundreds of resources in one state file) make every plan slower and every apply riskier, splitting state by environment or service (state isolation) keeps blast radius and plan time down.

Best Practices

  • Always use a remote backend with state locking (S3+DynamoDB, Terraform Cloud, etc.) the moment more than one person touches the infrastructure.
  • Require a reviewed terraform plan output (e.g., posted as a CI comment) before any apply runs, especially against production.
  • Split state per environment (dev/staging/prod) so a mistake in one can't touch another.
  • Use variables and modules instead of duplicating near-identical resource blocks across environments.

Interview questions

What is Terraform state and why is it required?

State is a JSON file (by default `terraform.tfstate`) that maps every resource block in your configuration to the real-world object Terraform created for it (an AWS instance ID, a DNS record, etc.). Terraform is declarative, your config describes the desired end state, not the steps to get there, so on every run it needs state to compute a diff between what exists now and what the config says should exist. Without state, Terraform would have no way to know whether a resource already exists, needs updating, or was deleted outside of Terraform.

Why is storing Terraform state locally a problem for a team, and what is the standard fix?

Local state is a single file on one person's machine, a second engineer running `terraform apply` has no idea what the first one already created, so both can independently "discover" no matching state and try to recreate resources, or worse, apply conflicting changes concurrently with no locking. The standard fix is a remote backend (S3+DynamoDB, Terraform Cloud, GCS, Azure Blob) that stores state centrally and supports locking, so only one apply can run at a time and everyone reads the same source of truth.

What is the difference between terraform plan and terraform apply, and why does that separation matter?

`plan` computes and displays the diff between current state and desired config without changing anything; it is a dry run. `apply` executes that diff against real infrastructure. Separating them means a human (or a CI approval gate) can review exactly what will be created, changed, or destroyed before anything actually happens, which is the core safety mechanism that makes infrastructure-as-code safer than manually clicking through a cloud console, nothing changes without a reviewed, explicit plan.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement