Plan, Apply, Drift Detection & Import
Safe Terraform change is a pipeline: a saved plan, a review of replaces, an apply of that file, then drift detection and a deliberate import or recreate.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
What does terraform plan -out produce?
Answer
A binary plan file for a later apply, plus a JSON form you can summarize and send to policy.
L2
Why is a bare apply dangerous?
Answer
Terraform may refresh and re-plan, picking up changes that were not in the review.
L3
How do you read a replace line?
Answer
As destroy plus create. Check data loss, DNS, and ARN churn before you approve.
L4
What is drift?
Answer
Reality disagrees with state or config. Sources include console edits, other automation, and half-finished applies.
L5
Import or recreate?
Answer
Import when the out-of-band object is the one you want to own. Recreate when the shape is wrong and you can afford to replace it.
L6
What does an import block not do?
Answer
It does not make config match the cloud. The next plan shows the remaining diff, which you reconcile on purpose.
L7
How do you undo a bad apply?
Answer
With a forward fix, a restore from backup for data, or a deliberate destroy. Reverting git alone leaves the cloud and state disagreeing.
Failure modes
Re-plan on apply
The merged commit or a console edit lands in the apply that reviewers did not see.
Auto-apply of a database replace
Dev speed settings leak onto a prod data root.
Import with no follow-up plan
State now points at an object whose config still disagrees, so the next apply mutates it by surprise.
Habitual -target
The rest of the graph stays unconverged and a later full apply is a surprise.
Scheduled drift plan with an apply role
A detector that can also delete is not a detector.
Misconceptions
git revert undoes an apply.
The cloud already changed. Revert the config only as part of a forward plan, or restore data from backup.
terraform refresh is the modern drift tool.
Plans refresh by default. A standalone refresh is legacy. Know the version you run.
Import copies the live settings into your .tf files.
Import binds an address to an ID. You still reconcile config to reality, or reality to config, on the next plan.
Interviewer traps
Describing a full pipeline product when the question was the plan file.
Say the CI page owns stages. Here the rule is apply the exact bytes you reviewed.
Using -target as the steady-state design.
Target is break-glass. Smaller roots are the design. Always finish with a full plan.
Design scenario
Same prompt for every reader.
Requirements
Saved plan artifacts, policy on the JSON, human review of replace and destroy, apply of that file, and a read-only scheduled drift plan.
Traffic / scale
Pull-request plans all day. A few prod applies, each tied to one artifact and an approval.
Latency
The plan comment is on the pull request before merge. Apply does not recompute the diff.
Consistency
After import, a follow-up plan is empty or the remaining diff is intentional.
Availability
Drift on a data store pages a human. Expected drift, such as an autoscaling desired count, is ignore_changes with an owner.
Failure assumptions
- Someone merges between plan and a re-plan apply.
- ClickOps changes a rule the same night.
- An imported bucket's config does not match the live ACL.
Constraints
- Prod apply consumes terraform plan -out.
- The drift job cannot mutate.
- Targeted apply is followed by a full plan.
Prompt
Prod plans are generated on laptops and applied later from CI with no plan file. A security group was edited in the console overnight. The morning apply also wants to replace a database.
API
What does the pull request publish, and what does the apply job pass to terraform apply?
Data
Which plan actions count as a replace, and which resource types add weight?
Architecture
Where do the artifact store, the drift schedule, and the import block sit?
A prod root must change today, and dev roots change many times a day
Prefer
Saved plans everywhere, a human gate on prod
Dev may auto-apply the plan it just produced. Prod applies only the file a reviewer saw, after policy passes.
- The artifact is the contract between review and apply.
- Replace and destroy lines are called out in the pull request.
- A later read-only plan catches console edits.
Alternative
Apply from a laptop whenever the folder looks right
The job re-plans against whatever the cloud and main happen to be.
- Reviewers approved a different diff.
- A database replace can ride along with a one-line rule edit.
- There is no run ID to connect the change to the serial.
Review a diff, then apply those bytes
Stage names and supply-chain checks live on the CI page. The Terraform rule is smaller: do not re-plan in the apply job.
- 1
fmt, validate, lint
Fail the pull request before anyone reads a noisy diff. - 2
plan -out and publish JSON
Same backend and var files as the target environment. Comment the summary. - 3
Humans read replace and destroy
Policy can block the obvious denies. A database replace still needs a person. - 4
apply the file
Record the run ID and the new state serial. A scheduled read-only plan watches for drift.
Overview
Safe change is a pipeline, not a laptop habit. Generate a plan artifact, review it, apply that artifact, detect drift, and import or deliberately recreate when reality and state diverge.
Pipeline stages, artifacts, and environment promotion in general live on CI/CD pipelines. The rule on this page is narrower: apply the exact bytes you reviewed.
The happy path
Decisions
- 1
1. Pull request
- next2. fmt validate lint
- 2
2. fmt validate lint
- next3. plan -out tfplan
- 3
3. plan -out tfplan
- next4. Policy pass?
- ?
4. Policy pass?
- no5. Block merge
- yes6. Review replaces
- 5
5. Block merge
- 6
6. Review replaces
- next7. apply tfplan
- 7
7. apply tfplan
Lesson map
Plan, Apply, Drift Detection & Import
Safe Terraform change is a pipeline: a saved plan, a review of replaces, an apply of that file, then drift detection and a deliberate import or recreate.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB pr["1. Pull request"] lint["2. fmt validate lint"] plan["3. plan -out tfplan"] policy["4. Policy pass?"] pr -->|1. Pull request to 2. fmt validate lint| lint lint -->|2. fmt validate lint| plan plan -->|3. plan -out tfplan| policy
Plans expire, and credentials at apply time still have to match the assumptions in the file. For prod, the controlled job plans, a human approves, and the apply job uses apply-time credentials on that same artifact.
How to read a plan
| Marker | Meaning | What you check |
|---|---|---|
+ create | A new address | Name collisions and unexpected regions |
~ update | In-place change | Security groups, IAM, encryption flags |
+/- replace | Destroy plus create | Data loss, DNS, ARN churn |
- destroy | Removal | A separate change, with an owner |
Tag reordering and default-attribute noise get quieter with lifecycle rules and provider upgrades. They are not a reason to skip the replace lines.
Drift
Drift means reality is not what state and config describe. Sources: console edits, another automation system, a half-applied run, and cloud features that mutate objects on their own.
| Approach | Cadence | Note |
|---|---|---|
| Scheduled plan with a read-only role | Hourly or daily | Page on a non-empty plan for critical roots |
| Terraform Cloud drift detection | Continuous-ish | Useful if you already run remote operations |
| Cloud-native drift, such as CloudFormation drift or Config | Varies | Complements a Terraform plan. It does not replace it for objects Terraform owns |
| Crossplane or an operator | Continuous | A different model: a reconcile loop |
Unexpected drift on a prod data store pages a human. Expected drift, such as an autoscaling desired capacity owned elsewhere, is ignore_changes with an owner. The graph page already warned that an ownerless ignore becomes permanent drift.
Decisions
- ?
1. Drift expected?
- yes2. ignore_changes plus owner
- no3. Page a human
- 2
2. ignore_changes plus owner
- 3
3. Page a human
- next4. Should Terraform own it?
- ?
4. Should Terraform own it?
- yes5. Import, then plan
- no6. Keep it outside this root
- 5
5. Import, then plan
- 6
6. Keep it outside this root
Import versus recreate
| Situation | Prefer |
|---|---|
| An out-of-band object that this root should own | terraform import or an import block (Terraform 1.5 and later) |
| A snowflake with the wrong shape | Recreate under Terraform and delete the snowflake on purpose |
| An address in state whose cloud object is gone | A plan will want to create it. Confirm before apply |
| A refactor that only renamed an address | state mv, not import |
Import does not copy live settings into your configuration. Immediately plan and either edit config to match reality or accept an intentional change.
import {
to = aws_s3_bucket.logs
id = "my-company-logs-prod"
}Import blocks are reviewable in git and friendlier for bulk adoption. The CLI remains useful for one-off surgery.
A toy blast-radius score
Weights are a gate hint, not a substitute for reading the plan. A security-group create scores 1. A database replace scores 6 for the replace plus 20 because the type is a data store and the actions include delete. The total is 27, which should demand another reviewer. The next page turns scores and policy into the actual gate.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Auto-apply
| Auto-apply on merge | Manual apply gate |
|---|---|
| Fast feedback | Fewer surprise prod changes |
| Needs policy and tests you trust | Humans are the last line, and they miss things too |
| Fits ephemeral and dev roots | Default for prod data planes |
Many orgs auto-apply dev and gate prod. Both still apply a saved file.
Targeted applies
terraform apply -target skips the illusion of whole-config convergence and can leave dangling dependencies. It is acceptable as break-glass. Follow it with a full plan. Prefer a smaller root over a habit of targeting.
Interview Q&A
The local plan is empty and CI wants replaces. Why?
Answer
Different provider versions, different variable sets, the wrong workspace or backend, or a missing var file. Align the lockfile and the environment inputs before you touch the resources.
Is terraform refresh still a command you run?
Answer
Standalone refresh is legacy. Plans refresh unless you disable that behavior. Know which Terraform version the pipeline runs.
How do you undo a bad apply?
Answer
Not with git revert alone. The cloud already changed. Prefer a forward-fix pull request, a restore from backup when data moved, or a deliberate destroy and recreate. Rolling state backward without rolling the cloud backward makes the next plan worse.
Import block or CLI import?
Answer
Import blocks live in reviewable config and scale to bulk adoption. The CLI is still the right tool for one surgical bind. Either way, the next plan is part of the change.
What is the difference between import and state mv?
Answer
Import binds an address to a cloud ID that state did not know. state mv renames an address Terraform already tracked. A refactor uses state mv. An adopted console resource uses import.
Who should run the hourly drift plan?
Answer
A read-only role. The job publishes a non-empty plan. It does not apply. An apply-capable drift cron is an unreviewed prod change.
When is -target acceptable?
Answer
Break-glass, with a ticket, followed by a full plan. If you need it every week, the root is too big.
What do you record after apply?
Answer
The run ID, the plan artifact name, and the new state serial. The next incident needs to connect the pull request to the backend object.
Pitfalls
- Generating the plan with a different var file than apply.
- Treating a green policy check as a substitute for reading a replace.
- Leaving an import commit whose following plan is a surprise ACL change.
- Disabling refresh to "keep plans stable" and then missing real drift.
- Explaining the whole CI product when the interviewer asked about the plan file.
The plan creates a security-group rule, updates a bucket tag, replaces a database, and destroys an old IAM policy. Say which lines auto-apply in dev, which lines stop a prod merge, and which command you refuse to use as the steady-state path.