Apply #
terraform apply is the command that actually changes your infrastructure. Everything you planned in plan gets executed here. Because the impact is real and immediate, understanding how apply works — including what happens when something goes wrong mid-way — is the difference between a controlled deployment and an unnecessary incident.
How Apply Works #
flowchart TD
A["terraform apply"] --> B{"Was a saved plan\nprovided?"}
B -->|"Yes: apply tfplan"| C["Execute the\nsaved plan directly"]
B -->|"No: plain apply"| D["Generate a new plan"]
D --> E["Show the plan\nAsk for 'yes' confirmation"]
E --> F["Confirmation received"]
C --> G["Execute according to\nthe dependency graph"]
F --> G
G --> H["Independent resources\nexecuted in parallel\n(max 10 concurrent)"]
H --> I["State updated after\neach resource completes"]
I --> J["Show summary\nApply complete!"]
I -.->|"Failed\nmid-way?"| K["Partial state:\nresources that already\nsucceeded remain recorded"]
style A fill:#e3f2fd,stroke:#1565c0
style J fill:#e8f5e9,stroke:#2e7d32
style K fill:#fff3e0,stroke:#e65100| Step | What It Does | Notes |
|---|---|---|
| 1. Check for a saved plan | If a plan file was given, use it | Safest — no re-planning |
| 2. Generate a plan (if needed) | Build the execution plan from config + state | Same as terraform plan |
| 3. Confirmation | User types yes | Skipped with -auto-approve or a saved plan |
| 4. Execute | Run operations per the dependency graph | Independent resources run in parallel |
| 5. Update state | After each resource completes | Incremental, not at the end |
| 6. Summary | Show add/change/destroy counts | Includes output values |
The key point in step 5: state is updated after each resource, not at the end after everything finishes. This is fundamental to understanding what happens when apply fails mid-way.
Interactive vs Automatic Apply #
flowchart TD
A["Three apply modes"] --> B["Interactive\n(terminal)"]
A --> C["Auto-approve\n(CI/CD)"]
A --> D["Saved plan\n(safest)"]
B --> B1["terraform apply\nThe user types 'yes'"]
C --> C1["terraform apply -auto-approve\nExecutes directly"]
D --> D1["terraform apply tfplan\nExecutes the saved plan"]
B1 --> E["⚠️ A new plan can\ndiffer from what\nwas reviewed earlier"]
C1 --> E
D1 --> F["✅ Exactly what\nwas reviewed"]
style E fill:#fff3e0,stroke:#e65100
style F fill:#e8f5e9,stroke:#2e7d32| Mode | Command | Confirmation | New Plan? | Best For |
|---|---|---|---|---|
| Interactive | terraform apply | Type yes | ✅ Yes | Development, exploration |
| Auto-approve | terraform apply -auto-approve | No | ✅ Yes | CI/CD with existing review |
| Saved plan | terraform apply tfplan | No | ❌ No | Production — safest |
# Interactive apply (default) — asks for confirmation before executing
terraform apply
# Output:
# Plan: 3 to add, 1 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 ← type this
# Automatic apply — no confirmation (for CI/CD)
terraform apply -auto-approve
# Apply from a saved plan — safest for production
terraform plan -out=tfplan
terraform apply tfplan
# No confirmation, no new plan — executes the saved plan directly
What Happens When Apply Fails Mid-Way #
This is the scenario that causes the most confusion. Because state is updated incrementally, a mid-apply failure produces partial state.
flowchart TD
A["Apply starts\ntarget: 5 resources"] --> B["✅ aws_vpc.main\ncreated, state updated"]
B --> C["✅ aws_subnet.public\ncreated, state updated"]
C --> D["❌ aws_instance.web\nFAILED! AMI not found"]
D --> E["⛔ aws_security_group\nnever executed"]
E --> F["⛔ aws_rds.database\nnever executed"]
D --> G["State now:\ncontains the VPC + subnet\nthat already succeeded"]
G --> H["Solution: Fix the\ncause of the failure"]
H --> I["terraform apply again"]
I --> J["✅ Terraform continues\nfrom the unfinished\nresources\nVPC & subnet are NOT\nrecreated"]
style B fill:#e8f5e9,stroke:#2e7d32
style C fill:#e8f5e9,stroke:#2e7d32
style D fill:#ffebee,stroke:#c62828
style E fill:#ffebee,stroke:#c62828
style F fill:#ffebee,stroke:#c62828
style J fill:#e8f5e9,stroke:#2e7d32SCENARIO: Apply fails after creating 2 of 5 resources
Before apply:
State: empty
Apply starts:
✓ aws_vpc.main → created, state updated
✓ aws_subnet.public → created, state updated
✗ aws_instance.web → FAILED (e.g. AMI not found)
- aws_security_group → never executed
- aws_rds.database → never executed
After failed apply:
State: contains aws_vpc.main and aws_subnet.public
(the resources that were successfully created)
What to do:
1. Fix the cause of the failure (e.g. change the AMI to a valid one)
2. Run terraform plan to see the current condition
3. Run terraform apply again
→ Terraform will continue from the resources not yet created
→ The VPC and subnet won't be recreated (already in state)
Terraform does not roll back automatically. This is intentional — automatic rollback can be dangerous if the resources already created contain data.
| When Apply Fails | What Happens | What to Do |
|---|---|---|
| Resource A succeeded, B failed | A is in state, B isn’t | Fix the cause, apply again |
| Permission error | The last resource failed | Check IAM policy, apply again |
| API rate limit error | Several resources failed | Reduce -parallelism, apply again |
| Corrupted state (rare) | Inconsistent state | Restore from a state backup |
Never runterraform destroyin response to a partial apply failure unless you’re truly sure you want to delete all resources. Just fix the problem and runterraform applyagain — Terraform will continue from where it left off.
Applying with Targets #
-target allows applying only specific resources, ignoring everything else.
flowchart TD
A["Need to apply\nspecific resources?"] --> B{"What's the reason?"}
B -->|"Emergency fix\nin production"| C["terraform apply\n-target=resource\n✅ Fine occasionally"]
B -->|"Debugging\na single resource"| D["terraform apply\n-target=resource\n✅ Fine occasionally"]
B -->|"Daily workflow"| E["❌ DON'T use\n-target"]
B -->|"Don't want to apply\nall resources"| F["Separate into a\ndifferent directory/module"]
E --> E1["Use the normal flow:\nterraform apply\nAll resources"]
style C fill:#e8f5e9,stroke:#2e7d32
style D fill:#e8f5e9,stroke:#2e7d32
style E fill:#ffebee,stroke:#c62828
style F fill:#e3f2fd,stroke:#1565c0# Apply only one specific resource
terraform apply -target=aws_instance.web
# Apply an entire module
terraform apply -target=module.vpc
# Apply several resources at once
terraform apply -target=aws_instance.web -target=aws_security_group.web
| Scenario | -target? | Alternative |
|---|---|---|
| Emergency fix in production | ✅ Fine | — |
| Debugging a single resource | ✅ Fine | — |
| Routine daily apply | ❌ Avoid | Apply all resources |
| Only want to apply part | ❌ Avoid | Split into modules/directories |
-targetis useful for emergencies or debugging, but avoid routine use. It can make state inconsistent with the configuration because dependencies between resources aren’t fully evaluated. If you frequently need-target, that’s a sign your configuration may need refactoring.
Apply Parallelism #
By default, Terraform runs up to 10 operations in parallel. This can be adjusted as needed.
flowchart LR
subgraph "Default (10 parallel)"
R1["Resource 1"] --> OK1["✅"]
R2["Resource 2"] --> OK2["✅"]
R3["Resource 3"] --> OK3["✅"]
DOT1["..."] --> DOT1O["✅"]
R10["Resource 10"] --> OK10["✅"]
end
subgraph "Limited (3 parallel)"
R11["Resource 1"] --> OK11["✅"]
R12["Resource 2"] --> WAIT["⏳ waiting"]
R13["Resource 3"] --> WAIT
WAIT --> OK12["✅"]
WAIT --> OK13["✅"]
end# Reduce parallelism (useful if the provider has rate limits)
terraform apply -parallelism=5
# Increase parallelism (be careful with provider rate limits)
terraform apply -parallelism=20
# For large infrastructures with many resources,
# AWS/GCP provider rate limits can become a bottleneck.
# Reducing -parallelism can help avoid throttling.
-parallelism | Best For | Risk |
|---|---|---|
3-5 | Providers with strict rate limits | Slower |
10 (default) | Most cases | Balanced |
15-20 | Very large infrastructures | API throttling |
Safe Apply Strategy for Production #
flowchart TD
A["1. terraform plan -out=tfplan\nGenerate and save the plan"] --> B["2. terraform show tfplan\nReview the plan (human-readable)"]
B --> C["3. Submit for approval\nPR review, Slack notification, etc."]
C --> D["4. Approval received"]
D --> E["5. terraform apply tfplan\nApply from the saved plan"]
E --> F["6. rm tfplan\nDelete the plan file containing sensitive data"]
F --> G["✅ Deployment complete\nAudit trail saved"]
style A fill:#e3f2fd,stroke:#1565c0
style E fill:#e8f5e9,stroke:#2e7d32
style G fill:#e8f5e9,stroke:#2e7d32# RECOMMENDED WORKFLOW FOR PRODUCTION:
# 1. Generate and save the plan
terraform plan -out=tfplan
# 2. Review the plan (human-readable)
terraform show tfplan
# 3. If there's CI/CD — submit for approval
# (PR review, Slack notification, etc.)
# 4. Once approved, apply from the saved plan
terraform apply tfplan
# 5. Delete the saved plan when done
rm tfplan
# Why use a saved plan?
# - No re-planning during apply — what's reviewed is exactly what's executed
# - No surprises from condition changes between review and apply
# - Clear audit trail: the plan file can be kept as evidence
Reading Apply Output #
$ terraform apply tfplan
aws_vpc.main: Creating...
aws_vpc.main: Creation complete after 2s [id=vpc-0abcdef1234567890]
aws_subnet.public: Creating...
aws_subnet.public: Creation complete after 1s [id=subnet-0abcdef1234567890]
aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Still creating... [20s elapsed]
aws_instance.web: Creation complete after 23s [id=i-0abcdef1234567890]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
instance_public_ip = "54.123.45.67"
Terraform shows progress in real time. Resources that take a long time (like EC2 instances or RDS) will show “Still creating…” every 10 seconds.
| Output Part | Meaning | Action |
|---|---|---|
Creating... | The resource is being created | Wait |
Still creating... [Xs elapsed] | Still in progress | Normal for large resources |
Creation complete after Xs [id=...] | Succeeded | Note the ID if needed |
Error: ... | Failed | Read the error, fix, apply again |
Apply complete! Resources: X added... | Done | Verify the results in the cloud console |
Summary #
- State is updated incrementally as each resource completes — not at the end. A mid-apply failure produces a valid partial state.
- There’s no automatic rollback — once a resource is created, Terraform won’t delete it if the next resource fails. Just fix the problem and re-apply.
- Three apply modes: interactive (type
yes), auto-approve (-auto-approvefor CI/CD), and saved plan (apply tfplanfor production — safest).- Use saved plans for production — make sure what’s applied is exactly what was reviewed.
-auto-approveis only for CI/CD with an existing review process — don’t use it directly in a production terminal.-targetis an emergency tool, not a routine workflow — overuse can cause inconsistent state.-parallelismcan be adjusted — reduce it if you hit provider API rate limits, increase it for very large infrastructures.- If apply fails mid-way, fix the cause then re-apply — Terraform will continue from the unfinished resources.