Terraform · Part 3

Terraform — Part 3 — The Core Workflow: init, plan, apply, destroy

Part 2 ran these four commands once without explaining them. This part opens each one up: what it actually does internally, why plan exists as a separate step from apply, and how to read a plan’s output before you ever type yes.

Quick idea: init prepares the working directory, plan previews what would change without touching anything, and apply carries that preview out — three distinct stages, each with a specific job.
init

Downloads providers and modules, sets up the backend, writes the lock file. Nothing in your infrastructure changes.

plan

Compares configuration against real infrastructure and proposes changes. Nothing in your infrastructure changes.

apply

Carries out a plan against real infrastructure. This is the step that actually changes anything.

Introduction

In Part 2, terraform init and terraform apply both ran successfully without much explanation of what either was actually doing behind the scenes. That was deliberate — the goal there was one working result. Here, each command gets opened up properly.

The question worth sitting with: if apply can generate its own plan automatically, why does plan exist as a separate command at all?

terraform init: Preparing the Working Directory

Think of terraform init like a contractor arriving on day one and unpacking their toolbox before cutting anything — nothing gets built yet, but nothing can be built without this step happening first.

Running terraform init does four things, in this order:

Provider install

Scans the configuration for direct and indirect provider references and downloads the matching plugins from the Terraform Registry.

Backend init

Reads any backend configuration and sets it up, migrating existing state if the backend has changed since the last init.

Module retrieval

Fetches the source for any module blocks from the locations given in their source arguments.

After providers are installed, Terraform writes the exact provider versions it selected into a dependency lock file, .terraform.lock.hcl. That file should be committed to version control — it’s what guarantees everyone on a team, and your CI pipeline, resolves the same provider versions rather than each independently picking “whatever satisfies the version constraint today.”

# Re-run init after adding a new provider or module,
# or after changing backend configuration. Safe to run
# repeatedly — it never touches your actual infrastructure.
terraform init

# Upgrade all providers/modules to the newest version
# allowed by your version constraints
terraform init -upgrade
Practical rule: terraform init is always safe to re-run. It only ever downloads plugins and sets up the backend — it never creates, changes, or deletes infrastructure.

terraform plan: Previewing Without Touching Anything

terraform plan reads the current state of your infrastructure, compares it against what the configuration says it should look like, and prints exactly what it would do to close that gap — without actually doing any of it.

# Show what would change, without changing anything
terraform plan

This is the step that makes Terraform safe to run against production. You get a complete, specific list of every resource that would be created, changed, or destroyed, in advance, and can read it as carefully as you’d read a code review before merging.

Reading a Plan’s Output

Each proposed change in a plan is prefixed with a symbol that tells you exactly what kind of change it is:

Symbol Meaning
+ Create — a new resource will be added.
~ Update in place — an existing resource will be modified without being destroyed.
-/+ Destroy and re-create — the change can’t be made in place, so the resource is deleted and rebuilt. Worth reading carefully; this often means downtime.
- Destroy — the resource will be removed entirely.
Key rule: a -/+ line is the one to slow down on. It means the resource is being deleted and rebuilt from scratch, not adjusted — for anything stateful, that’s a very different risk profile from an in-place ~ update.

Useful flags for plan: -out=FILENAME saves the plan to a file so it can be applied later exactly as reviewed, rather than re-planned at apply time (the recommended pattern for CI/CD automation); -destroy previews what a full teardown would look like without running it; -target=ADDRESS narrows planning to a specific resource and its dependencies.

terraform apply: Carrying Out the Plan

terraform apply executes the operations proposed in a plan. Run on its own, it generates a fresh plan first, prints it, and prompts for confirmation before touching anything:

# Generates a plan, shows it, then asks "Do you want to
# perform these actions?" before doing anything
terraform apply

If you already saved a plan with terraform plan -out=tfplan, you can apply that exact plan without a second confirmation prompt, since the review already happened at plan time:

# Apply a previously saved plan file exactly as reviewed,
# with no further confirmation prompt
terraform apply tfplan
Production note: -auto-approve skips the confirmation prompt entirely. HashiCorp’s own guidance is that it should only be used when nothing outside your Terraform workflow can change the same infrastructure — otherwise you lose the one moment where a human catches a plan that doesn’t match what they expected.

terraform destroy: Tearing It Down

terraform destroy removes every resource the configuration manages. Under the hood it’s a convenience wrapper for terraform apply -destroy, and it accepts most of the same flags apply does.

# Preview what a full teardown would remove, without doing it
terraform plan -destroy

# Actually remove everything this configuration manages
terraform destroy

# Remove only one resource and its dependents, not everything
terraform destroy -target aws_instance.example

Like apply, destroy prompts for confirmation before doing anything, and that prompt is worth reading properly rather than reflexively typing yes — especially against a shared environment.

The Full Workflow at a Glance

[.tf files]  ─── terraform init ───▶  [Providers + modules installed, lock file written]

[.tf files]  ─── terraform plan ───▶  [Proposed changes printed: + ~ -/+ -]
                                        (nothing in real infrastructure has changed yet)

[Plan]       ─── terraform apply ──▶  [Confirm "yes"] ──▶  [Real infrastructure changed]

[.tf files]  ─── terraform destroy ▶  [Confirm "yes"] ──▶  [Everything managed is removed]

Why plan Is a Separate Step

Going back to the question from the introduction: apply can generate its own plan automatically, so why bother running plan separately?

Because in a real team environment, the person writing the configuration change and the person authorising it to run against production are often not the same person, and not even working at the same time. Saving a plan with -out and applying that exact file later is what turns “trust me, this looks right” into something someone else can actually read and approve — the same discipline as reviewing a diff before merging application code, applied to infrastructure.

Quick Reference Summary

Command Changes Real Infrastructure?
terraform init No — downloads providers/modules, sets up backend.
terraform plan No — previews proposed changes only.
terraform apply Yes — carries out a plan after confirmation.
terraform destroy Yes — removes everything the configuration manages.

Final Thoughts

Four commands cover the entire day-to-day Terraform workflow, and only two of them are capable of changing anything real. That split is the whole safety model: read-only steps you can run as often as you like, and change-making steps that always show you the plan first.

Everything from here on in this series — variables, state, modules — plugs into the same four commands. Nothing about the workflow itself changes as configurations get more complex.

Key takeaway: if you’re ever unsure what a Terraform command is about to do, the answer is almost always in whether it’s init/plan (read-only) or apply/destroy (changes real infrastructure, after a confirmation prompt).
Next in this series

Terraform — Part 4 — HCL Basics: Providers, Resources, and Blocks. We slow down on the syntax itself — what a block is, what arguments and meta-arguments look like, and how resources reference each other.