Top 10 Terraform Commands Every DevOps Engineer Must Master
· Sourabh G Kulkarni
Beyond basic init and apply: essential production commands, flags, and state manipulation techniques separating junior Terraform users from platform engineers.

Most engineers learn Terraform through a simple three-step tutorial: terraform init, terraform plan, and terraform apply.
That mental model works fine for a personal sandbox. But the moment you manage multi-account cloud environments, coordinate with five team members over shared state, or need to refactor a production database out of a root module without destroying it, that basic workflow collapses.
Operating Terraform in production requires knowing how to manipulate the state graph, preview deterministic artifacts, import legacy resources without downtime, and debug complex expressions before committing them to Git.
Terraform Production Command Engine & State Lifecycle
Figure 1: The Terraform lifecycle across staging, UAT, and production environments.
Here are the top 10 Terraform commands every DevOps and Platform Engineer needs in their production playbook.
1. terraform fmt -recursive
Canonical Code Formatting & Clean Git Diffs
# Format every .tf file in the current directory and all submodules
terraform fmt -recursive
In any team setting, code formatting debates are a waste of engineering time. Terraform includes an opinionated, built-in AST formatter that standardizes indentation, aligns equal signs (=), and formats lists uniformly.
Why It Matters in Production:
- Clean Pull Request Diffs: When every engineer uses
terraform fmt, your pull requests only highlight real infrastructure mutations—not whitespace noise. - Pre-Commit Automation: Run this command in a pre-commit hook or as a lightweight GitHub Actions / GitLab CI linter (
terraform fmt -check). If a developer forgets to format their code, the pipeline fails fast before triggering expensive cloud checks.
2. terraform init (With Key Flags)
Initializing Backends, Providers, and Dependency Locks
# Upgrade providers within version constraints and re-initialize
terraform init -upgrade
# Pass dynamic remote backend config per environment (Dev, UAT, Prod)
terraform init -backend-config="bucket=mycompany-tf-state-prod" \
-backend-config="key=networking/terraform.tfstate" \
-backend-config="dynamodb_table=mycompany-tf-locks" \
-reconfigure
terraform init is the bridge between your static .tf files and the outside world. It scans your code, downloads the required cloud provider plugins (AWS, Azure, GCP, Cloudflare), and establishes a connection to your remote state storage.
Pro-Tips for Enterprise Pipelines:
- The
.terraform.lock.hclFile: Always commit your dependency lockfile to Git. It records exact cryptographic hashes for downloaded provider binaries, preventing unexpected upstream provider updates from breaking your pipeline. -reconfigurevs-migrate-state: When switching backend buckets between environments in CI/CD, always use-reconfigureto discard local cached backend settings and avoid accidentally syncing state across accounts.
3. terraform validate
Fast Pre-Flight Syntax & Type Checking
# Validate configuration without calling cloud APIs
terraform validate
terraform validate verifies that your HCL configuration is syntactically valid and internally consistent. It verifies attribute names, checks that variable types match their definitions, and ensures required provider arguments are supplied.
Key Distinction:
validate is completely offline—it does not make HTTP requests to AWS or Azure to verify whether a subnet ID actually exists. Because it runs locally in under two seconds, it should be the very first step in every pull request validation workflow.
4. terraform plan -out=tfplan
Generating Deterministic Plan Artifacts
# Calculate diff and save plan to an immutable file
terraform plan -out=tfplan -detailed-exitcode
The execution plan computes the difference between your desired code and the live state stored in your backend. In production, never run terraform plan purely to inspect the terminal and then follow it with a plain terraform apply.
┌────────────────────────────────────────────────────────────────────────┐
│ The Production Plan-Apply Workflow │
├─────────────────────────┬──────────────────────────────────────────────┤
│ 1. PR Merge Trigger │ Runs: `terraform plan -out=tfplan` │
│ 2. Pipeline Artifact │ Stores `tfplan` securely in CI storage │
│ 3. Peer Approval Gate │ Engineers review the exact recorded diff │
│ 4. Apply Stage │ Runs: `terraform apply tfplan` │
└─────────────────────────┴──────────────────────────────────────────────┘
Why -out=tfplan Is Mandatory in CI/CD:
If you run terraform apply without an input file, Terraform computes a fresh plan right before applying. If another engineer merged a change or a cloud resource changed in the 10 minutes between your plan review and the apply click, you will blindly apply unreviewed mutations.
The -out=tfplan flag saves the computed plan to a cryptographically locked file. Running terraform apply tfplan guarantees that only the exact reviewed diff will be executed.
5. terraform state list & terraform state show
Querying the State Without Opening Raw JSON
# List every resource address tracked by the current state
terraform state list
# Inspect the full live metadata and attributes of one specific resource
terraform state show aws_db_instance.primary
Your state file contains thousands of lines of raw JSON mapping your code to cloud resource identifiers. You should never manually edit or parse raw terraform.tfstate files with a text editor.
When to Use Them:
- Use
terraform state listto quickly verify what resources Terraform believes it owns in this environment. - Use
terraform state show <address>to grab dynamically assigned cloud attributes (e.g., an automatically allocated Private IP, VPC endpoint DNS, or KMS key ARN) without logging into the cloud console.
6. Declarative import Blocks (and terraform import)
Adopting Pre-Existing Cloud Resources Without Downtime
# Modern declarative import block (Terraform 1.5+)
import {
to = aws_s3_bucket.legacy_data
id = "my-pre-existing-enterprise-bucket"
}
# Automatically generate the corresponding HCL configuration
terraform plan -generate-config-out=generated_resources.tf
In the real world, you rarely start with an empty greenfield cloud account. You frequently inherit VPCs, IAM roles, or RDS databases created manually via the console.
Historically, bringing existing resources into Terraform required running imperative CLI commands: terraform import aws_s3_bucket.data my-bucket.
Since Terraform 1.5, the modern standard is declarative import blocks. You write the intent in code, run plan, and let Terraform generate the exact configuration block automatically. This makes infrastructure imports fully reviewable in pull requests.
7. terraform state mv
Refactoring and Moving Resources Without Recreating Them
# Refactor an inline resource into a reusable module without downtime
terraform state mv aws_security_group.web module.networking.aws_security_group.web
When engineering teams mature, they extract monolithic root resources into reusable modules. But if you simply copy code from main.tf into modules/networking/main.tf, Terraform's graph sees this as:
- Delete the old security group (
aws_security_group.web). - Create a brand new security group (
module.networking.aws_security_group.web).
In production, deleting and recreating a security group or RDS database means instant outage.
terraform state mv instructs Terraform: "This resource simply moved its address in code; do not touch the real cloud resource." When you run terraform plan afterward, it reports 0 to add, 0 to change, 0 to destroy.
8. terraform apply -replace="<address>"
Targeted, Safe Resource Re-Creation
# Safely mark an EC2 instance or TLS cert for recreation in the next plan
terraform apply -replace="aws_instance.bastion"
Historically, if an EC2 instance became unhealthy, corrupted its disk, or failed an OS configuration, engineers used terraform taint aws_instance.bastion.
However, taint mutated the remote state file immediately on your laptop before you ever reviewed a plan.
Starting in Terraform 0.15+, -replace replaced the taint workflow. It behaves declaratively: it leaves your state untouched and instructs the plan engine: "Calculate what happens if we recreate this single resource, and show me the plan before touching it."
9. terraform console
The Interactive REPL for Testing Complex Logic
# Open the interactive Terraform shell
terraform console
Terraform HCL includes dozens of built-in functions for manipulating strings, IP subnets, lists, and maps. Testing complex logic directly inside a multi-tier plan is slow and frustrating.
Using terraform console, you can test expressions interactively against your live state:
> cidrsubnet("10.0.0.0/16", 8, 2)
"10.0.2.0/24"
> lookup({ env = "prod", tier = "gold" }, "env", "dev")
"prod"
> [for s in ["web", "api", "db"] : upper(s)]
[
"WEB",
"API",
"DB",
]
It is the fastest way to verify subnet slicing, string formatting, and conditional expressions before applying them to production infrastructure.
10. terraform state rm
Gracefully Severing Ties Without Destroying Real Hardware
# Remove a production database from Terraform management without deleting it in AWS
terraform state rm aws_rds_cluster.core_database
Sometimes you need to retire a Terraform module or hand a mission-critical database over to a dedicated DBA team, but you never want to delete the actual cloud resource.
If you simply delete the resource block from your .tf file and run apply, Terraform will faithfully call the AWS API and delete the database.
Running terraform state rm deletes the resource reference from the state file while leaving the real cloud infrastructure completely untouched. Once removed, you can safely delete the HCL code from Git.
Summary Cheat Sheet
| Command | Primary Use Case | When to Run It |
|---|---|---|
terraform fmt -recursive | Standardizes code indentation and formatting. | Local pre-commit hook / PR lint stage. |
terraform init -upgrade | Downloads provider plugins and connects backend. | After repo clone or provider version bumps. |
terraform validate | Fast offline syntax and type checking. | Every PR pre-flight check in CI. |
terraform plan -out=tfplan | Computes deterministic preview artifact. | Pull Request review and pipeline merge. |
terraform apply tfplan | Executes the exact pre-computed plan. | Post-merge deployment pipeline with approval gate. |
terraform state list | Lists all tracked resource addresses. | Troubleshooting state contents and drift. |
terraform state show | Shows live attributes of a single resource. | Inspecting IPs, ARNs, and dynamic attributes. |
import { ... } | Adopts pre-existing cloud resources into code. | Legacy infrastructure onboarding. |
terraform state mv | Refactors code addresses without recreating infra. | Moving resources into reusable modules. |
-replace="<addr>" | Safely targets a resource for destruction & recreation. | Healing unhealthy nodes, refreshing certs. |
terraform console | Interactive REPL shell for evaluating functions. | Debugging CIDR subnets, loops, and maps. |
terraform state rm | Unlinks cloud resources from state without deleting. | Decommissioning Terraform management safely. |
The Production Rule to Remember
Mastering Terraform is not about memorizing CLI flags. It is about understanding the relationship between your code, the state file, and the live cloud API.
When you treat your state file with respect, verify your plan artifacts before applying, and isolate environments into dedicated state backends (see Terraform across Dev, UAT, and Production), Terraform transforms from a risky operational tool into a deterministic, reliable delivery platform.