Hitesh Sahu
Hitesh SahuHitesh Sahu
  1. Home
  2. ›
  3. posts
  4. ›
  5. …

  6. ›
  7. 6 Workflow

Loading ⏳
Fetching content, this won’t take long…


💡 Did you know?

🍯 Honey never spoils — archaeologists found 3,000-year-old jars still edible.

🍪 This website uses cookies

No personal data is stored on our servers however third party tools Google Analytics cookies to measure traffic and improve your website experience. Learn more

Loading ⏳
Fetching content, this won’t take long…


💡 Did you know?

🦥 Sloths can hold their breath longer than dolphins 🐬.
Terraform

    AI-AgenticAI

    AI-DeepLearning

    AI-GenAI

    AI-Infrastructure

    AI-Machine-Learning

    AI-Math

    AWS

    Azure

    Hobbies

    kubernetes

    Management

    Programming

    Terraform
    • Terraform Certification Path

    • Terraform Basics

    • Terraform Configuration Management

    • TF Modules: How to Use & Create

    • TF State & Backend Management

    • Terraform Core Workflow & Commands

    • IaC Concepts & TF Overview

    • TF Cloud Capabilities & Workflow

    • TF CMD Cheatsheet

    • Terraform Index


    Z_Appendix

    0-root

Cover Image for Terraform Core Workflow & Commands
Terraform

Terraform Core Workflow & Commands

The complete Terraform workflow — Write, Plan, Apply, and Destroy — with what each step does internally, essential commands, the production CI/CD pattern using plan files, importing existing infrastructure, and verbose logging.

Terraform
Cloud
Infrastructure as Code
IaC
DevOps
← Previous

TF State & Backend Management

Next →

IaC Concepts & TF Overview

Terraform Core Workflow & Commands

The Core Workflow: Write → Plan → Apply

flowchart LR

Write["✍️ Write\n.tf config files"]

-->Init["terraform init\ndownload providers\n& modules"]

Init

-->Fmt["terraform fmt\nformat HCL"]

Fmt

-->Validate["terraform validate\ncheck syntax"]

Validate

-->Plan["terraform plan\ncompare config vs state\nshow diff"]

Plan

-->Apply["terraform apply\ncall provider APIs\nupdate state"]

Apply

-->|"repeat"| Write

Apply

-->|"teardown"| Destroy["terraform destroy\ndelete all resources\nclears state"]

What happens at each step

Step Command What Terraform does internally
Init terraform init Downloads provider plugins to .terraform/ — reads required_providers in config. Downloads modules. Creates or updates .terraform.lock.hcl.
Format terraform fmt Rewrites .tf files to the canonical HCL style (indentation, alignment). Idempotent.
Validate terraform validate Parses HCL and checks types, required attributes, and expression validity. Does NOT call any provider APIs — no credentials needed.
Plan terraform plan Reads state, calls provider's Read for each existing resource, compares desired config to actual state, outputs the diff. No changes made.
Apply terraform apply Runs plan again (or executes a saved plan file), then calls provider APIs to create/update/delete. Updates state file after each resource.
Destroy terraform destroy Equivalent to terraform apply -destroy — creates a plan to delete everything in state, then applies it.

Initialize

# Standard init — downloads providers and modules
terraform init

# Upgrade providers and modules to newest allowed versions
terraform init -upgrade

# Don't download plugins (use cached)
terraform init -get-plugins=false

# Don't verify plugin signatures (not recommended)
terraform init -verify-plugins=false

terraform init must be re-run after:

  • Adding a new provider or module
  • Changing a provider version constraint
  • Changing the backend configuration

Format and Validate

# Reformat all .tf files to canonical HCL style
terraform fmt

# Show what would change without rewriting
terraform fmt -diff

# Check formatting (exit 1 if not formatted) — useful in CI
terraform fmt -check -recursive

# Validate syntax and type correctness
terraform validate

# Validate output as JSON (for tool integration)
terraform validate -json

# Validate without connecting to backend
terraform validate -backend=false

Plan

# Show what would change
terraform plan

# Save plan to a file (use in CI — prevents race conditions)
terraform plan -out=tfplan

# Destroy plan
terraform plan -destroy

# Target a specific resource
terraform plan -target=aws_instance.web

# Override a variable
terraform plan -var="instance_type=t3.large"

# Use a variable file
terraform plan -var-file=prod.tfvars

# Skip refreshing real-world resource state (faster, but may miss drift)
terraform plan -refresh=false

Apply

# Interactive apply (shows plan + confirmation prompt)
terraform apply

# Apply without confirmation prompt (use in CI after reviewing plan)
terraform apply --auto-approve

# Apply a saved plan file — executes exactly what was planned, no re-plan
terraform apply tfplan

# Target a specific resource only
terraform apply -target=aws_instance.web

# Limit parallel resource operations (default: 10)
terraform apply --parallelism=5

# Pass variable on command line
terraform apply -var="region=us-west-2"

# Apply without refreshing state (skip read of real-world resources)
terraform apply -refresh=false

Destroy

# Destroy all managed infrastructure
terraform destroy

# Without confirmation prompt
terraform destroy --auto-approve

# Preview what would be destroyed
terraform plan -destroy

# Destroy a specific resource only
terraform destroy -target=aws_instance.web

Production CI/CD Pattern: Plan File

In a CI/CD pipeline (GitHub Actions, GitLab CI, Jenkins), always use a saved plan file to prevent the plan from changing between review and apply:

# CI Step 1 — PR pipeline: plan and save
terraform plan -out=tfplan

# CI Step 2 — human reviews the plan output
# (GitHub Actions: annotate PR with plan output)

# CI Step 3 — merge to main: apply exactly what was planned
terraform apply tfplan

Why this matters: without a plan file, terraform apply runs a new plan before applying. Between your PR review and the merge, someone else might have applied a change — the new plan could differ from what you reviewed. The plan file makes apply deterministic.

# Example: GitHub Actions workflow
jobs:
  plan:
    steps:
      - run: terraform plan -out=tfplan -no-color 2>&1 | tee plan.txt
      - name: Comment plan on PR
        uses: actions/github-script@v6
        with:
          script: |
            const plan = require('fs').readFileSync('plan.txt', 'utf8')
            github.rest.issues.createComment({ body: `\`\`\`\n${plan}\n\`\`\`` })

  apply:
    needs: plan
    if: github.ref == 'refs/heads/main'
    steps:
      - run: terraform apply -auto-approve tfplan

Import Existing Infrastructure

Use terraform import when infrastructure already exists in the cloud and you want to bring it under Terraform management.

# Import an EC2 instance (AWS resource ID = instance ID)
terraform import aws_instance.web i-1234567890abcdef0

# Import an S3 bucket
terraform import aws_s3_bucket.assets my-bucket-name

# Import a resource into a module
terraform import module.networking.aws_vpc.main vpc-0abc123def456

# Import using for_each key
terraform import 'aws_instance.web["production"]' i-1234567890abcdef0

Terraform 1.5+ declarative import block (preferred over the CLI command):

# Write the resource block first
resource "aws_instance" "web" {
  # ... configuration
}

# Add an import block — Terraform generates the config on plan/apply
import {
  to = aws_instance.web
  id = "i-1234567890abcdef0"
}

Run terraform plan -generate-config-out=generated.tf to have Terraform write the resource config for you based on the real resource's attributes.


Outputs

# List all output values
terraform output

# Get a specific output
terraform output instance_public_ip

# Output in JSON format (for scripting)
terraform output -json

# Output a specific value in raw format (no quotes)
terraform output -raw instance_public_ip

State Commands

# List all resources in state
terraform state list

# Show details of a specific resource
terraform state show aws_instance.web

# Move/rename a resource in state (no API calls)
terraform state mv aws_instance.old aws_instance.new

# Move resource into a module
terraform state mv aws_instance.web module.compute.aws_instance.web

# Remove resource from state (resource stays in cloud, Terraform forgets it)
terraform state rm aws_instance.web

# Pull remote state to stdout
terraform state pull

# Manually unlock state (if a crash left it locked)
terraform force-unlock LOCK_ID

# Refresh state from real-world resources
terraform apply -refresh-only

Verbose Logging

Enable when debugging provider issues, unexpected plan differences, or authentication problems:

# Log levels: TRACE, DEBUG, INFO, WARN, ERROR
export TF_LOG=DEBUG
terraform apply

# Log only Terraform core (not providers)
export TF_LOG_CORE=TRACE

# Log only provider calls
export TF_LOG_PROVIDER=DEBUG

# Write logs to a file
export TF_LOG_PATH=./terraform.log

# Disable logging
export TF_LOG=off

TRACE is the most verbose — it logs every API call the provider makes. Use DEBUG for most debugging; TRACE when debugging provider auth or HTTP-level issues.


Workspace Commands

terraform workspace new staging        # create new workspace
terraform workspace select staging     # switch to workspace
terraform workspace list               # list all workspaces
terraform workspace show               # current workspace name
terraform workspace delete staging     # delete workspace (must be empty)

Related Posts

  • IaC Concepts & TF Overview — why IaC exists, declarative vs imperative, and Terraform's core value proposition before diving into the workflow
  • TF State & Backend Management — the state file that plan reads and apply updates; remote backends and state locking
  • Terraform Basics: Providers, Resources, and Initialization — provider plugin architecture, resource blocks, and data sources that the workflow operates on
  • TF Cloud Capabilities & Workflow — how Terraform Cloud runs the same workflow remotely with VCS integration and Sentinel policy checks
Hitesh Sahu
Written by Hitesh Sahu, a passionate developer and blogger.

Wed Feb 25 2026

Share This on

← Previous

TF State & Backend Management

Next →

IaC Concepts & TF Overview

Terraform/6-Workflow
Let's work together
+49 176-2019-2523
hiteshkrsahu@gmail.com
WhatsApp
Skype
Munich 🥨, Germany 🇩🇪, EU
Playstore
Hitesh Sahu's apps on Google Play Store
Need Help?
Let's Connect
Navigation
  Home/About
  Skills
  Work/Projects
  Lab/Experiments
  Contribution
  Awards
  Art/Sketches
  Thoughts
  Contact
Links
  Sitemap
  Legal Notice
  Privacy Policy

Made with

NextJS logo

NextJS by

hitesh Sahu

| © 2026 All rights reserved.