SaveMyCert
Log in
5 of 5 free questions left today·for unlimited practice
Core Terraform workflow

terraform apply, destroy, and fmt: executing and maintaining changes

11 min readTerraform 004 · Core Terraform workflowUpdated

terraform apply executes the changes proposed in an execution plan — creating, updating, or destroying real infrastructure and recording the results in state — while terraform destroy tears down everything Terraform manages, and terraform fmt rewrites configuration into HashiCorp's canonical style. Apply is the only core-workflow command that modifies infrastructure, which is why it demands explicit approval: it shows you a plan and waits for you to type yes, unless you pass -auto-approve or hand it a saved plan file that was already reviewed. This lesson covers the approval prompt, applying saved plans, how apply walks the dependency graph with up to ten concurrent operations, destroying with terraform destroy and its modern equivalent terraform apply -destroy, targeted destroys, and the fmt flags (-check, -recursive, -diff) that keep formatting enforceable in CI. It closes with a validate recap and the pipeline order the exam expects: fmt, init, validate, plan, apply.

What you’ll learn
  • Execute infrastructure changes with terraform apply and explain when the approval prompt appears and when it is skipped
  • Apply a saved plan file and contrast it with letting apply generate a fresh plan
  • Control concurrency with -parallelism and describe how apply walks the resource dependency graph
  • Destroy managed infrastructure with terraform destroy and terraform apply -destroy, including targeted destroys with -target
  • Enforce canonical formatting with terraform fmt, using -check and -recursive in CI pipelines
  • Order fmt, init, validate, plan, and apply correctly in an automated pipeline

terraform apply: executing the plan

terraform apply executes the actions in an execution plan against real infrastructure and records the results in the state file. Run without arguments, it first generates a fresh plan — exactly as terraform plan would — prints it, and then pauses for confirmation. Only after you approve does it start creating, updating, and destroying resources. As each operation completes, apply writes the resulting resource attributes (server-assigned IDs, IP addresses, ARNs) into state, which is how later plans know what already exists.

$ terraform apply

Terraform will perform the following actions:

  # google_compute_instance.app will be created
  + resource "google_compute_instance" "app" {
      + name         = "app-server"
      + machine_type = "e2-medium"
      + zone         = "europe-west1-b"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_compute_instance.app: Creating...
google_compute_instance.app: Creation complete after 14s [id=projects/demo/zones/europe-west1-b/instances/app-server]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

If some operations fail partway through, apply does not roll back: resources already created stay created and are recorded in state, and the errors are reported so you can fix the configuration and apply again. While it runs, apply holds the state lock (on backends that support locking) so concurrent runs cannot corrupt state.

The approval prompt and -auto-approve

The interactive prompt exists because apply is the one command that changes real infrastructure: Terraform shows the plan and accepts only the literal word yes before proceeding. Anything else aborts with no changes made. This is the default whenever apply generates its own plan.

$ terraform apply -auto-approve
$ terraform destroy -auto-approve

-auto-approve skips the prompt entirely — apply plans and immediately executes. It exists for automation: a scripted pipeline or an ephemeral test environment has no human to type yes. The exam framing to remember is that there are exactly two ways apply proceeds without an interactive approval: you passed -auto-approve, or you supplied a saved plan file (covered next), where the review already happened at plan time. In shared or production environments, prefer the saved-plan pattern over -auto-approve — auto-approving a fresh plan means executing changes nobody looked at.

Applying a saved plan file

Passing a saved plan file makes apply execute exactly the actions that were reviewed: terraform plan -out=tfplan followed by terraform apply tfplan. Because the plan was already generated and (presumably) reviewed, apply does not re-plan and does not prompt — it goes straight to execution. This is the standard CI/CD shape: the plan stage produces the artifact and a human or policy check approves it; the apply stage consumes the identical artifact.

$ terraform plan -out=tfplan
$ terraform show tfplan     # review what will happen
$ terraform apply tfplan
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Details the exam likes: the saved plan embeds the input variable values that were in effect at plan time, so you do not pass -var or -var-file again at apply. The file is binary and may contain sensitive values — inspect it with terraform show (or terraform show -json for tooling) and store it securely. And the plan is tied to the state snapshot it was computed from: if the state has changed since, Terraform rejects the stale plan and you must generate a new one. That staleness check is the guarantee that "what you reviewed is what runs" cannot be silently violated.

Parallelism: how apply orders and batches work

Apply walks Terraform's resource dependency graph, not your file order: resources that depend on each other run strictly in dependency order, and resources with no relationship run concurrently. By default Terraform performs at most 10 concurrent operations; -parallelism=n changes that ceiling for a single run of plan or apply.

$ terraform apply -parallelism=5    # gentler on rate-limited APIs
$ terraform apply -parallelism=30   # more concurrency for large graphs

Lowering parallelism helps when a provider's API throttles you or when ordering pressure on a fragile backend matters more than speed; raising it can shorten applies with many independent resources. Two clarifications that show up in questions: parallelism is a runtime flag, not something you set in configuration, and it never overrides dependencies — a subnet that references a VPC will wait for the VPC no matter how high the limit is set.

terraform destroy: tearing down managed infrastructure

terraform destroy destroys every remote object managed by the current configuration — everything tracked in the state file. Like apply, it first shows you a plan (all lines marked -) and requires you to type yes; -auto-approve skips the prompt. Destruction runs in reverse dependency order: dependents are destroyed before the things they depend on, so an instance goes before its subnet, and the subnet before its VPC.

$ terraform destroy

Terraform will perform the following actions:

  # aws_instance.web will be destroyed
  - resource "aws_instance" "web" {
      - ami           = "ami-0c101f26f147fa7fd"
      - instance_type = "t3.micro"
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?

  Enter a value: yes

Destroy complete! Resources: 1 destroyed.

terraform destroy is an alias for terraform apply -destroy — the same destroy planning mode expressed as an apply flag. To preview a teardown without executing it, run terraform plan -destroy, or save it for later execution with terraform plan -destroy -out=tfplan and apply that file. Destroy only touches what Terraform manages: resources created outside Terraform, or already removed from state, are untouched. It is routine for ephemeral environments — spin up a test stack, run the tests, destroy it — and rare, deliberate, and heavily reviewed for production.

Targeted operations with -target

-target restricts a plan, apply, or destroy to specific resource addresses (plus anything they depend on). To remove a single resource while leaving the rest of the stack running, target it in a destroy:

$ terraform destroy -target=aws_instance.web
$ terraform apply -target=module.network    # apply just one module subtree

The flag accepts resource addresses (aws_instance.web), module paths (module.network), and can be repeated to name several targets. Terraform automatically includes required dependencies in a targeted apply, and required dependents in a targeted destroy — you cannot destroy a VPC and leave its subnets orphaned in state.

Know the official caveat: -target is for exceptional situations — recovering from errors, working around bugs, surgically removing one resource — not for routine use. Routinely applying subsets leaves your infrastructure drifting from what a full plan would produce, and Terraform prints a warning on every targeted run for exactly that reason. If you want a resource gone permanently, the right fix is to delete it from configuration and run a normal apply.

terraform fmt: canonical formatting

terraform fmt rewrites Terraform configuration files (.tf and .tfvars) into HashiCorp's canonical style — consistent indentation, aligned = signs within argument blocks, normalized spacing — and prints the names of the files it changed. Formatting is purely cosmetic: fmt never changes what the configuration does, and it performs no validity checking beyond needing to parse the files. By default it processes only the current directory; -recursive descends into subdirectories, which you almost always want in a repository with modules.

$ terraform fmt
main.tf

$ terraform fmt -recursive          # format the whole tree
$ terraform fmt -check -recursive   # CI gate: list unformatted files, exit non-zero
$ terraform fmt -diff               # show the changes as a diff

-check makes fmt a verification instead of a rewrite: it modifies nothing, lists any files that are not canonically formatted, and exits with a non-zero status if any exist — which is precisely how CI pipelines fail a build on unformatted code. -diff displays the formatting changes, and combining -check with -diff shows reviewers exactly what needs fixing. A frequent distractor pairs fmt with validation: fmt does not validate your configuration, and validate does not format it — they are separate commands with separate jobs.

Validate recap and the full pipeline order

terraform validate is the correctness counterpart to fmt's style: it verifies that the configuration is syntactically valid and internally consistent — legal argument names, resolvable references, matching types — without contacting any cloud API or reading state. It requires an initialized directory (provider schemas come from init), so CI jobs without backend credentials run terraform init -backend=false first. Use -json for machine-readable diagnostics.

Put together, the canonical automated pipeline the exam expects is:

OrderCommandPurposeKey flags
1terraform fmtEnforce canonical style-check, -recursive, -diff
2terraform initInstall providers/modules, configure backend-backend=false, -upgrade
3terraform validateSyntax and consistency check, offline-json
4terraform planProduce and review the execution plan-out, -detailed-exitcode
5terraform applyExecute the reviewed plansaved plan file, -auto-approve, -parallelism
terraform destroyTear down managed infrastructure-auto-approve, -target

Memorize the flag-to-command pairings in that table — a classic question format offers -check or -auto-approve against the wrong command and asks which invocation is valid.

Tip. Expect questions on when the apply approval prompt appears and the two ways to bypass it (-auto-approve versus applying a saved plan file), on destroy being equivalent to apply -destroy and previewable with plan -destroy, and on -target as an exceptional-use flag. The default parallelism of 10 and the -parallelism flag are frequent factual questions. For fmt, know -check (non-zero exit for CI), -recursive, and -diff — and that fmt formats but never validates, while validate checks consistency but never formats.

Key takeaways
  • terraform apply is the only core-workflow command that changes infrastructure: it plans, waits for you to type yes, executes in dependency order, and records results in state while holding the state lock.
  • Apply skips the approval prompt in exactly two cases: -auto-approve, or applying a saved plan file that was already reviewed at plan time.
  • A saved plan (plan -out=tfplan, apply tfplan) embeds variable values, is inspected with terraform show, and is rejected as stale if the state changed since it was created.
  • Apply performs up to 10 concurrent operations by default; -parallelism=n adjusts the ceiling at runtime but never overrides dependency ordering.
  • terraform destroy removes everything in state, prompting like apply and destroying in reverse dependency order; it is an alias for terraform apply -destroy, and terraform plan -destroy previews it.
  • -target limits plan/apply/destroy to named resource or module addresses plus their required dependencies — an exceptional-use tool that Terraform warns about, not a routine workflow.
  • terraform fmt rewrites .tf and .tfvars files to canonical style; -recursive covers subdirectories, -check exits non-zero on unformatted files for CI, and -diff shows the changes. fmt never validates.
  • Pipeline order: fmt -check, init, validate, plan -out, apply the saved plan.

Frequently asked questions

When does terraform apply skip the approval prompt?

In two cases only: when you pass -auto-approve, or when you apply a saved plan file (terraform apply tfplan). With a saved plan the review already happened when the plan was generated, so Terraform executes it directly. A plain terraform apply always generates a fresh plan and waits for you to type the literal word yes.

What is the difference between terraform destroy and terraform apply -destroy?

Nothing in effect — terraform destroy is an alias for terraform apply -destroy. Both plan the destruction of every resource in state, show the plan with every line marked -, and require approval unless -auto-approve is passed. Related: terraform plan -destroy previews the teardown without executing, and you can save that destroy plan with -out and apply it later.

What does terraform fmt -check do in a CI pipeline?

It turns fmt into a read-only gate: no files are rewritten, files that deviate from canonical style are listed, and the command exits with a non-zero status if any exist — failing the build. Teams typically run terraform fmt -check -recursive so subdirectories and modules are covered, and add -diff so the log shows exactly what needs reformatting.

Does terraform apply roll back if something fails midway?

No. Terraform has no rollback: resources created before the failure remain and are recorded in state, and the failed and unattempted operations are reported. You fix the cause and run apply again — Terraform picks up from the recorded state and completes the remaining changes. This is also why state is updated incrementally as each operation finishes.

How many resources does Terraform change at once, and can I control it?

By default, at most 10 resource operations run concurrently. Pass -parallelism=n to terraform plan or terraform apply to change the ceiling for that run — lower it when a provider API rate-limits you, raise it to speed up large graphs of independent resources. It is a CLI flag, not a configuration setting, and dependencies always take precedence over concurrency.

Is using -target a normal way to work with Terraform?

No — HashiCorp documents -target as being for exceptional situations, such as recovering from errors or surgically removing one resource, and Terraform prints a warning on every targeted run. Routine targeted applies let real infrastructure drift from what a full plan would produce. To permanently remove a resource, delete it from the configuration and run a normal apply instead.

Test yourself on this topic
Practice questions with full explanations.
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.