The direct answer
You don't need a new read-only preview mode — the opt-in already exists. Run:
pulumi preview --refresh=false
This tells the engine to skip the refresh step, so preview builds its diff from the stored state snapshot instead of calling the provider's read APIs. In a least-privilege environment where those read calls are what fail with an authorization error, this is the scoped fix — no mock providers, no disabled plugins, no broadened IAM policy.
Why preview fails in the first place
By default, Pulumi refreshes state before computing a preview: it asks each provider to read the live resource and compares the result against the snapshot. That read is what requires permissions like Describe*/Get*/List* on AWS or equivalent read roles elsewhere. When the credentials lack them, the provider returns an authorization error and the preview aborts — before any diff is shown.
Confirmed vs. likely: the refresh-before-diff behavior and the --refresh=false escape hatch are well-established CLI behavior. The exact permission set each provider needs during refresh varies by provider and resource type, so treat any specific IAM action list as something to verify against your provider's docs, not as universal.
The trade-off: fidelity vs. least privilege
Skipping refresh means the diff is computed against the last known state, not reality. Concretely:
- No drift detection. If someone changed or deleted a resource in the console, preview won't see it. The plan may look clean while the real apply behaves differently.
- Ghost updates. If a resource was deleted out-of-band, the snapshot still thinks it exists; preview may show a no-op or an in-place update where apply will actually attempt a create or fail.
- Apply may still need reads. Some providers do read-before-write during
pulumi up. A preview that passes with --refresh=false doesn't prove the update phase will succeed under the same credentials.
Keeping drift detection without standing read rights
A practical pattern is to separate the two concerns instead of choosing one permanently:
- Run day-to-day CI previews with
--refresh=false under the least-privilege role, so pull requests get fast diffs without read access. - Run a scheduled, separate
pulumi refresh (or preview --refresh) under a read-only role — nightly or before releases — purely to surface drift. Read-only roles are usually far easier to approve than write-capable ones. - Before any consequential apply, do one refreshed preview so the plan reflects reality.
This gives you snapshot-based previews as the default while preserving drift detection as an explicit, auditable step — which is arguably better than silently relying on every preview to refresh.
Verify before you rely on it
Flag behavior can shift across CLI versions, so confirm on your setup:
pulumi version
pulumi preview --refresh=false --diff
Check that the run completes without authorization errors and that the diff matches your expectation from the snapshot. Then compare once against a refreshed preview (with read credentials) to see exactly what drift you'd otherwise miss. If your provider still issues reads during preview even with the flag, that one diagnostic — the provider and CLI version — determines whether this is a flag-semantics issue or a provider bug worth reporting.