skip to content

People describe `pulumi preview` as the equivalent of `terraform plan`. Where does that comparison break down?

level: seniorimportance: should knowfreq 32%

answer

  1. same intent, different mechanics
  2. read-only that runs your code
  3. no refresh unless you ask
  4. unknown values hide resources
  5. approved diff is recomputed, not replayed

basics

~20 s

The comparison holds at the level of intent but leaks in three places: a Pulumi preview executes your program, so code side effects happen at preview time; it does not refresh against the cloud unless asked; and resources created inside a callback on an unknown value are invisible until apply.

solid answer

~50 s

Both commands answer the same question — what would change — but they get there differently. `terraform plan` evaluates a configuration and, by default, refreshes recorded state against the live cloud first; `pulumi preview` runs your actual program, and does not refresh unless you pass `--refresh`, so drift can be silently absent from the output. Because the program executes, any side effect in that code — an HTTP call, a file write — happens during a preview that you thought was read-only. And values that only exist after creation are unknown during preview, so anything registered inside a callback on such a value simply does not appear; the preview can under-report what `up` will create. Finally the CI shape differs: Terraform can save a plan and apply that exact artifact later, whereas `pulumi up` by default re-runs the program and previews again before applying.

code

bash · 2 lines
bash
pulumi preview --refresh --diff
pulumi up --refresh --yes

go deeper

for a junior

Know that pulumi preview shows what would change without changing anything, and pulumi up is what actually applies — the same pairing as plan and apply in Terraform.

for a middle

Explain the mechanics behind the seams: a preview executes the program rather than parsing a document, and Pulumi treats refreshing state against the cloud as an opt-in step rather than a default.

for a senior

Demonstrate the operational consequences — side effects firing during a supposedly read-only step, drift hidden by a default preview, and a preview that under-reports resources hidden behind unknown values.

for a principal

Own the assurance question: what exactly did the approver approve, how wide is the window between approval and apply, and what automated policy check compensates when the applied diff is recomputed rather than replayed.

## The mapping that does hold `pulumi preview` is read-only and tells you what `pulumi up` intends to do; `terraform plan` is read-only and tells you what `terraform apply` intends to do. Both classify changes into create, update, replace and delete, both need provider credentials and network access to do their job, and both are the thing you put in front of a human before production changes. If the interviewer only wants the mapping, that is the answer. The question is usually asking for the seams. ## Leak 1 — a preview runs your code Terraform evaluates a configuration document. Pulumi *executes a program*. Everything in that program runs during a preview: loops, helper functions, and anything the author put in there that touches the outside world. If the program fetches a list of tenants over HTTP, that HTTP call happens on every preview. If it writes a file, generates a value from the clock, or increments a counter in some external system, that happens during the "read-only" step too. The consequences are real: a preview in CI needs whatever credentials those side effects require, a preview can fail for reasons unrelated to infrastructure, and a non-deterministic program can produce a preview that does not match the `up` that follows minutes later. The mitigation is a rule, not a flag — keep the program pure, with all external reads expressed as provider data lookups rather than ad-hoc calls. ## Leak 2 — refresh is opt-in `terraform plan` refreshes: before diffing, it re-reads the managed resources from the provider so the plan is computed against reality, unless you disable it. `pulumi preview` and `pulumi up` do **not** refresh by default; they diff your program against the recorded state as it stands. Pulumi exposes refresh as a separate `pulumi refresh` command and as a `--refresh` flag on preview and up. The operational consequence is the one that bites: if someone changed a resource in the console, a default Pulumi preview may show "no changes" because the recorded state still matches the program — the drift is invisible until something else forces a read. Teams that care run with refresh enabled, and accept the extra API calls and time that costs on a large stack. ## Leak 3 — unknowns can hide whole resources Both tools have values that cannot be known until create time — an assigned ARN, a generated ID. Terraform renders these as `(known after apply)`, but the *resource* still appears in the plan, because the graph comes from a static configuration. In Pulumi, such a value is an output that is unknown during preview, and a callback registered on an unknown output is not executed at preview time. So if a resource is constructed *inside* one of those callbacks, the preview does not know it exists and does not list it. You get a preview showing five resources and an `up` that creates eight. Terraform's static graph makes this failure mode structurally impossible; Pulumi's dynamic one does not. The mitigation is stylistic: register resources at the top level and pass outputs into their arguments, rather than constructing resources inside a callback on a value that does not exist yet. ```typescript // Hazard: this resource is invisible at preview time when db.address is unknown. db.address.apply(addr => new aws.ssm.Parameter("db-addr", { type: "String", value: addr })); // Better: register at the top level and let the engine resolve the input. new aws.ssm.Parameter("db-addr", { type: "String", value: db.address }); ``` ## Leak 4 — the CI shape The two-stage pipeline most teams want is: produce a diff on the pull request, have a human approve *that* diff, then apply exactly what was approved. Terraform's saved plan file is built for that — plan on the PR, store the artifact, apply the artifact on merge. Pulumi's default flow is different: `pulumi up` runs the program again, previews, and asks for confirmation interactively (`--yes` or `--skip-preview` in automation). So the common pipeline is "preview on the PR for review, then up on merge", where the applied diff is recomputed rather than replayed. In practice the window is small and teams live with it, but you should be able to say plainly that the approval gate is weaker than replaying a saved artifact, and that anything which changed in between — someone else's apply, a drifted resource, a moved input — is inside that window. ## How to say it Lead with "same intent, different mechanics", then name the seams: it runs code, it does not refresh by default, unknowns can hide resources, and the approved diff is recomputed rather than replayed. That is a senior answer because each seam has an operational consequence, not just a definitional one.

  • Why can a Pulumi preview report fewer resources than the following up actually creates?
    Because a value that only exists after creation is unknown during preview, and a callback registered on an unknown value is not executed. Any resource constructed inside such a callback is therefore not registered at preview time and does not appear in the output. Terraform cannot hit this because its graph comes from static configuration. The fix is to register resources at the top level and pass the output in as an argument.
  • What does it change operationally that Pulumi does not refresh before previewing?
    Drift can be invisible: if someone edited a resource in the console, the recorded state still matches your program, so a default preview reports no changes and the divergence persists until something reads the resource. Teams that care run `pulumi refresh` on a schedule or pass `--refresh` on preview and up, trading extra provider API calls and wall-clock time on large stacks for an accurate diff.
  • How would you design a Pulumi pipeline so a human approves what actually gets applied?
    Run `pulumi preview --refresh --diff` on the pull request and publish the output as a comment so the diff is reviewed; gate the merge on that approval; then run `pulumi up --yes` on merge from a protected job with a least-privilege role. Add policy checks to the preview so a surprising replace or delete fails automatically, and keep the merge-to-apply window short so the recomputed diff cannot drift far from the reviewed one.

saying these in an interview costs you the question

  • Says pulumi preview is read-only, so nothing in the program executes
  • Assumes Pulumi refreshes state before every preview like Terraform does
  • Believes a preview always lists every resource an up will create
  • Thinks pulumi up applies a previously saved preview artifact by default
  • Treats plan and preview as interchangeable with no operational differences

context