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
| Concept | What it is | Command |
|---|---|---|
| Provider | Plugin that talks to a specific API (AWS, Azure, GCP, Cloudflare...) | Declared in a required_providers block |
| Resource | A single infrastructure object Terraform manages | resource "type" "name" { ... } |
| State | Terraform's record of what it created, mapped to real object IDs | terraform show, terraform state list |
| Plan | A dry-run diff between config and state | terraform plan |
| Apply | Executes a plan against real infrastructure | terraform 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.
| Command | Description | Copy |
|---|---|---|
terraform init | Initialize a working directory: download providers/modules and configure the backend. | |
terraform fmt | Rewrite configuration files to the canonical style. | |
terraform validate | Check configuration for syntax and internal consistency errors, without touching real infrastructure. | |
terraform version | Show the Terraform and provider versions in use. |
| Command | Description | Copy |
|---|---|---|
terraform plan | Show a dry-run diff between configuration and current state. | |
terraform plan -out=tfplan | Save a computed plan to a file, so the exact reviewed plan is what gets applied. Treat tfplan as a sensitive artifact, it can contain secrets in cleartext, so never commit it or share it broadly. | |
terraform show | Show the current state, or a saved plan file, in human-readable form. | |
terraform graph | Generate a visual dependency graph of resources. |
| Command | Description | Copy |
|---|---|---|
terraform apply | Apply changes to reach the desired state, prompting for confirmation. | |
terraform apply tfplan | Apply a previously saved plan file exactly - no new diff computed at apply time. | |
terraform apply -auto-approve | Apply without an interactive confirmation prompt - for CI pipelines, not interactive use. | |
terraform destroy | Destroy every resource Terraform currently manages in state. |
| Command | Description | Copy |
|---|---|---|
terraform state list | List every resource tracked in the current state. | |
terraform state show <resource> | Show current state attributes for one resource. | |
terraform state rm <resource> | Remove a resource from state without destroying the real infrastructure behind it. | |
terraform import <resource> <id> | Bring an existing, unmanaged resource under Terraform management. | |
terraform workspace new <name> | Create a new workspace - an isolated state, e.g. for a separate environment. |
Syntax
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
# Standard workflow - review before anything changes
terraform init # downloads providers, configures the backend
terraform plan # shows the diff; nothing is changed yet
terraform apply# 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.tfstateto Git; it often contains plaintext secrets and immediately causes merge conflicts between teammates. - Running
terraform applydirectly without reviewingterraform 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
planshows 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 planandapplyruntime 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 planoutput (e.g., posted as a CI comment) before anyapplyruns, 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.