terraform apply
terraform apply is the command that actually changes your infrastructure. It takes the diff that plan describes — resources to create, update, or destroy — and executes the corresponding provider API calls, then records the new reality in state. Because it mutates real cloud resources, apply defaults to an interactive confirmation step: it shows you the plan and waits for you to type yes before touching anything. Understanding that confirmation flow, how to apply a saved plan, and what the output means is the difference between a safe rollout and an outage.
The default interactive apply
Run with no arguments, apply first generates a fresh plan, prints it, and pauses for approval. This is the safest mode for day-to-day work because you review the exact changes immediately before they happen.
terraform apply
Output:
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_s3_bucket.assets will be created
+ resource "aws_s3_bucket" "assets" {
+ bucket = "acme-assets-prod"
+ id = (known after apply)
+ arn = (known after apply)
}
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
aws_s3_bucket.assets: Creating...
aws_s3_bucket.assets: Creation complete after 2s [id=acme-assets-prod]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Only the literal string yes approves — y, Y, or an empty line all abort with no changes made. The configuration below produced that plan:
resource "aws_s3_bucket" "assets" {
bucket = "acme-assets-prod"
}
OpenTofu users run
tofu apply; the flags, approval flow, and output format are identical, so everything on this page applies to both tools.
Applying a saved plan
The plan that an interactive apply shows you is regenerated at apply time, so in theory the world could have changed between your review and your yes. To eliminate that gap, save a plan to a file and apply exactly it. When you pass a saved plan file, apply skips the prompt entirely — the file is itself the approval.
terraform plan -out=tfplan
terraform apply tfplan
Output:
aws_s3_bucket.assets: Creating...
aws_s3_bucket.assets: Creation complete after 2s [id=acme-assets-prod]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
This plan-then-apply split is the standard pattern in CI/CD pipelines: a plan stage produces the artifact, a human (or policy check) reviews it, and a separate apply stage consumes the exact same artifact. There is no ambiguity about what will run.
Skipping approval with -auto-approve
-auto-approve tells apply to proceed without the interactive prompt. It generates a plan and runs it immediately.
terraform apply -auto-approve
This is convenient but removes your last safety net, so it is only appropriate in specific contexts.
| Scenario | Safe to auto-approve? | Why |
|---|---|---|
| Local development against throwaway resources | Yes | Mistakes are cheap and reversible |
| CI applying a previously reviewed saved plan | Use the plan file instead | The saved plan already encodes approval; no flag needed |
| CI with no prior plan review | No | Nothing reviews the diff before it executes |
| Production from a developer laptop | No | One typo can destroy live infrastructure |
Prefer
apply tfplanoverapply -auto-approvein pipelines. Applying a saved plan guarantees the executed changes are byte-for-byte what was reviewed, whereas-auto-approvere-plans against live state that may have drifted since review.
Useful apply options
| Flag | Effect |
|---|---|
-auto-approve | Skip the interactive yes prompt |
-input=false | Disable all prompts; fail rather than ask (use in CI) |
-parallelism=n | Limit concurrent resource operations (default 10) |
-target=ADDR | Apply only the given resource and its dependencies |
-var / -var-file | Supply input variable values |
-replace=ADDR | Force-recreate a specific resource on this apply |
-refresh=false | Skip refreshing state before planning (faster, riskier) |
Partial failures
Provider API calls can fail mid-apply — a quota limit, an invalid argument, a transient network error. Terraform applies resources according to the dependency graph, so a failure stops dependent work but does not roll back resources that already succeeded. Anything created before the error is real and is recorded in state.
aws_vpc.main: Creation complete after 3s [id=vpc-0a1b2c3d]
aws_subnet.public: Creating...
╷
│ Error: creating EC2 Subnet: InvalidParameterValue: CIDR 10.0.0.0/25 is
│ not a valid subnet for VPC vpc-0a1b2c3d
│
│ with aws_subnet.public,
│ on network.tf line 12, in resource "aws_subnet" "public":
│ 12: resource "aws_subnet" "public" {
╵
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Here the VPC was created and saved; only the subnet failed. Terraform does not undo the VPC. Fix the configuration and re-run apply — because state already knows the VPC exists, the next plan shows only the remaining subnet, making apply naturally resumable. This is why apply is idempotent: re-running it converges toward the desired state rather than duplicating work.
Reading apply output
Every apply ends with a one-line summary you should read carefully:
Apply complete! Resources: 3 added, 1 changed, 2 destroyed.
The three counts map directly to the +, ~, and - symbols from the plan. A destroyed count you did not expect is a red flag — it means something forced replacement or removal. During execution, each resource logs Creating..., Modifying..., or Destroying... lines with elapsed timers, and finally Outputs: prints any defined output values once everything settles.
Best Practices
- In pipelines, always
applya saved plan file produced by an earlier reviewedplanrather than re-planning at apply time. - Reserve
-auto-approvefor ephemeral or local environments; never wire it to production from a workstation. - Add
-input=falsein automation so a missing variable fails the job instead of hanging on a prompt. - Read the final summary line — an unexpected
destroyedcount means stop and investigate before celebrating. - Treat partial failures as resumable: fix the root cause and re-apply rather than manually cleaning up created resources.
- Lower
-parallelismonly when a provider rate-limits you; the default of10is right for most cases. - Keep state locking enabled so two simultaneous applies cannot corrupt your state.