All posts

Getting Started with Terraform: Infrastructure as Code for 2026

Post Share

Clicking through a cloud console to spin up servers feels productive right up until the day you need to do it again — on a second account, in a second region, without forgetting the security group you set by hand three months ago. Manual infrastructure doesn't scale, and it doesn't survive the person who built it leaving the team.

Infrastructure as Code (IaC) fixes this by treating your servers, networks, and databases the same way you treat application code: written down, reviewed, versioned, and repeatable. Terraform is the most widely adopted IaC tool, and in 2026 it remains the default choice for provisioning across AWS, Azure, GCP, and hundreds of other providers. This guide takes you from zero to a working, safe Terraform workflow.

Terraform turns declarative configuration files into real cloud infrastructure through a plan and apply workflow

What Infrastructure as Code Actually Buys You

Before the syntax, it's worth being precise about why teams adopt IaC, because the benefits shape how you should use the tool:

  • Repeatability — the same configuration produces the same infrastructure every time, whether it's the first environment or the fifth.
  • Reviewability — infrastructure changes arrive as pull requests. A risky firewall change gets the same scrutiny as a risky code change.
  • Auditability — Git history becomes the record of who changed what and when. No more mystery resources nobody remembers creating.
  • Disaster recovery — if an environment is destroyed, you can rebuild it from configuration instead of memory.

Terraform delivers these through a declarative model: you describe the desired end state, and Terraform figures out the create, update, and delete operations needed to reach it. You don't write the steps — you write the destination.

Installing Terraform

Terraform ships as a single binary. On macOS with Homebrew:

brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform -version

On Linux, download the binary or use the package repository:

wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

Confirm it works with terraform -version. You should see the version and the platform.

Your First Configuration

A Terraform project is just a directory of .tf files. Terraform reads all of them together, so how you split things across files is purely for human organization.

Start with a provider — the plugin that knows how to talk to a specific platform's API. Here's a minimal example that creates a local file, so you can follow along without a cloud account:

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

resource "local_file" "hello" {
  filename = "${path.module}/hello.txt"
  content  = "Provisioned by Terraform\n"
}

Two blocks are doing the work here. The terraform block pins which providers the project needs and which versions are acceptable — ~> 2.5 means "2.5 or any newer 2.x". The resource block declares a thing you want to exist: a local_file named hello, with a filename and content.

The Core Workflow: init, plan, apply

Terraform's day-to-day loop is three commands. Learn these and you know 80% of Terraform.

terraform init downloads the providers your configuration needs into the working directory. Run it once per project and again whenever you add or upgrade a provider:

terraform init

terraform plan compares your configuration against the real world and prints exactly what it intends to do — without changing anything. This is your safety net. Read it every time:

terraform plan

You'll see a summary like Plan: 1 to add, 0 to change, 0 to destroy. A + marks a resource to be created, ~ a change, and - a destroy. If a plan proposes destroying something you didn't expect, stop and investigate before applying.

terraform apply executes the plan. By default it shows the plan again and waits for you to type yes:

terraform apply

After it finishes, hello.txt exists. Edit the content value, run plan again, and Terraform will show a single in-place update. This tight plan-then-apply loop is the whole discipline: you never change infrastructure blind.

Moving to a Real Provider

Swapping local for a cloud provider changes the resources, not the workflow. A minimal AWS example that provisions a single S3 bucket:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

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

resource "aws_s3_bucket" "assets" {
  bucket = "my-app-assets-2026"

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Authenticate with your cloud credentials — for AWS, that usually means environment variables or a named profile — then run the same init, plan, apply cycle. The ManagedBy = "terraform" tag is a small habit worth keeping: it tells anyone browsing the console that a resource is owned by code and should not be edited by hand.

Variables and Outputs

Hardcoding values makes configuration brittle. Variables parameterize it, and outputs expose useful values after an apply.

variable "environment" {
  description = "Deployment environment name"
  type        = string
  default     = "dev"
}

resource "aws_s3_bucket" "assets" {
  bucket = "my-app-assets-${var.environment}"

  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

output "bucket_name" {
  value = aws_s3_bucket.assets.bucket
}

Now the same configuration produces a dev, staging, or production bucket depending on the variable you pass — terraform apply -var="environment=production". The output block prints the bucket name when the apply completes and makes it available to other tooling.

State: The One Concept That Trips Everyone Up

Terraform records what it created in a state file (terraform.tfstate). State is how Terraform maps your configuration to real-world resources — without it, Terraform would have no idea that aws_s3_bucket.assets corresponds to an actual bucket it made last week.

Three rules will save you real pain:

  • Never edit state by hand. Use terraform state subcommands if you must manipulate it.
  • Never commit state to Git. It contains resource metadata and sometimes secrets. Add *.tfstate* to .gitignore.
  • Use remote state for any team. Local state on one laptop can't be shared or locked, so two people applying at once will corrupt it.

For a team, configure a backend so state lives in shared, lockable storage:

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

The dynamodb_table provides state locking — if a teammate is mid-apply, your run waits instead of racing. This one setting prevents the most common way teams corrupt shared infrastructure.

Modules: Don't Repeat Yourself

Once you've written a good configuration for, say, a web service, you'll want to reuse it. A module is just a directory of Terraform files you can call from elsewhere, passing variables in:

module "web_service" {
  source      = "./modules/web-service"
  environment = "production"
  instance_count = 3
}

Modules let you standardize how your organization builds infrastructure — one reviewed, tested definition of "a web service" instead of copy-pasted blocks that drift apart. You can source them from a local path, a Git repository, or the public Terraform Registry, which hosts thousands of community and vendor-maintained modules.

A Safe Workflow for Teams

Terraform gives you the tools, but discipline is what keeps production safe. A workflow that scales:

  1. Store configuration in Git and require pull requests for every change.
  2. Run terraform plan in CI on each PR and post the output as a comment so reviewers see exactly what will change.
  3. Apply only from a protected branch, ideally through a CI job rather than a laptop, so applies are consistent and logged.
  4. Use remote state with locking so concurrent applies can't collide.
  5. Separate environments into distinct state files or workspaces so a mistake in dev can never touch production.

If this sounds like the CI/CD practices you already apply to application code, that's exactly the point — Infrastructure as Code means infrastructure earns the same guardrails.

Where to Go Next

You now have the core loop — write configuration, init, plan, apply — plus the concepts that separate a toy setup from a production one: variables, state, backends, and modules. The most valuable next step is to convert one piece of real infrastructure you currently manage by hand. Import it, describe it in code, and run a plan until it reports no changes. That "no changes" plan is the moment your infrastructure becomes reproducible.

From there, explore terraform fmt and terraform validate for clean, correct code, and look at policy tools like OPA or Sentinel when you're ready to enforce organization-wide rules. But the fundamentals in this guide will carry you a long way — most of Terraform is just the plan-and-apply discipline, applied consistently.

More on similar topics

#aws AWS Lambda & Serverless Architecture: Complete 2026 Guide 10 May 2026 #nodejs Testing Node.js Applications: A Practical Guide for 2026 28 July 2026 #cicd CI/CD Pipeline Best Practices: A Production-Ready Guide for 2026 12 May 2026