Fix: Pulumi Not Working — Output<T>, Stack References, Secrets, State Backend, and Preview vs Up
Quick Answer
How to fix Pulumi errors — Output<T> can't be unwrapped synchronously, stack reference not found, secret leaks in stack outputs, state backend lock, ResourceOptions parent missing, and refresh drift.
The Error
You log a Pulumi Output<string> and get a Promise-like thing:
const bucket = new aws.s3.Bucket("my-bucket");
console.log(bucket.id);
// OutputImpl<id> { ... } — not the actual string.Or you reference a stack output and it’s undefined:
const ref = new pulumi.StackReference("org/network/prod");
const vpcId = ref.getOutput("vpcId");
// undefined at preview time.Or pulumi up complains the state is locked:
error: getting secrets manager: passphrase must be set with
PULUMI_CONFIG_PASSPHRASE or PULUMI_CONFIG_PASSPHRASE_FILEOr a deploy partially succeeds and you can’t redeploy:
error: failed to register new resource ... -- conflicting resource registrationsWhy This Happens
Pulumi differs from Terraform in important ways:
Output<T>is async. Pulumi resource properties returnOutput<T>, not the raw value. They resolve duringpulumi upafter dependencies create. You can chain with.apply(), interpolate withpulumi.interpolate, but you can’t directly read them in your TypeScript flow.- Stack references read another stack’s outputs. They work — but
getOutputreturnsOutput<T | undefined>. The “undefined” you see during preview is genuine; the value is known only after the target stack has been deployed. - State backend is configurable. Pulumi Cloud (default), S3, Azure Blob, GCS, file backend. Each has different locking and secret encryption. The “passphrase” prompt is from file/cloud backends that encrypt your stack secrets.
- Resource URN identifies a resource. Renaming a logical name (
new aws.s3.Bucket("my-bucket")→new aws.s3.Bucket("renamed")) creates a new resource and tries to destroy the old. Use aliases to handle renames safely.
Fix 1: Handle Output<T> Correctly
Three primary patterns:
.apply() — transform inside the callback:
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("my-bucket");
// WRONG — bucket.id is Output<string>, not string:
const arn = `arn:aws:s3:::${bucket.id}`;
// RIGHT — apply runs after bucket.id resolves:
const arn = bucket.id.apply((id) => `arn:aws:s3:::${id}`);pulumi.interpolate — template literal that handles Outputs:
import * as pulumi from "@pulumi/pulumi";
const arn = pulumi.interpolate`arn:aws:s3:::${bucket.id}`;
// Returns Output<string> with the full ARN once resolved.pulumi.interpolate is essentially apply + string concat in a cleaner form. Prefer it for simple string composition.
pulumi.all([...]) — combine multiple Outputs:
const policyArn = pulumi.all([bucket.arn, bucket.region]).apply(
([arn, region]) => `${arn}-${region}-policy`,
);Common Mistake: Trying to await an Output<T>. It’s not a Promise; awaiting silently produces wrong values. Use .apply() instead.
For logging during development:
bucket.id.apply((id) => console.log(`Bucket ID: ${id}`));The log appears during pulumi up after the bucket is created.
Fix 2: Configure the State Backend
Pulumi has multiple backend choices. View / change:
pulumi login # Default — Pulumi Cloud (app.pulumi.com)
pulumi login s3://my-bucket # S3 backend
pulumi login azblob://... # Azure
pulumi login gs://my-bucket # GCS
pulumi login file://~/.pulumi # Local fileFor non-Cloud backends, Pulumi encrypts secrets with a passphrase. Set:
export PULUMI_CONFIG_PASSPHRASE=your-secret-passphrase
# Or:
export PULUMI_CONFIG_PASSPHRASE_FILE=~/.pulumi-passphraseFor Pulumi Cloud, secrets are managed by the service — no passphrase needed.
For CI/CD with self-hosted backends:
# GitHub Actions example
- env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
run: pulumi up --yesPro Tip: For new teams, start with Pulumi Cloud (free tier covers most usage). Switch to S3 backend only if you need to run fully self-hosted.
Fix 3: Use Secrets Correctly
For values that shouldn’t appear in plaintext (passwords, API keys):
pulumi config set --secret db_password "s3cr3t"This encrypts and stores in the stack config. Read in code:
const config = new pulumi.Config();
const dbPassword = config.requireSecret("db_password");
// dbPassword is Output<string> with the secret flag set.When you apply over a secret, the result inherits the secret flag — so derived outputs are also encrypted at rest:
const connectionString = dbPassword.apply(
(pw) => `postgres://user:${pw}@host:5432/db`,
);
// connectionString is also marked secret.Common Mistake: Logging a secret to console.log. Pulumi tries to redact secrets in output (replaces with [secret]), but if you transform the secret into a non-secret string (e.g. with .apply() that drops the secret flag), it can leak.
To make any computed value secret:
const safe = pulumi.secret(someValue);This explicitly marks someValue as secret so logs and state file encrypt it.
Fix 4: Stack References
To consume outputs from another stack:
const network = new pulumi.StackReference("myorg/network/prod");
// Get a specific output (returns Output<unknown>):
const vpcId = network.getOutput("vpcId");
const subnetIds = network.getOutput("subnetIds");
// Use it:
const sg = new aws.ec2.SecurityGroup("app-sg", {
vpcId: vpcId.apply((id) => id as string),
});Type-safety:
const vpcId = network.requireOutput("vpcId") as pulumi.Output<string>;
const subnetIds = network.requireOutput("subnetIds") as pulumi.Output<string[]>;requireOutput errors if the output doesn’t exist; getOutput returns Output<unknown | undefined>.
For the target stack to expose outputs:
// In the network stack:
export const vpcId = vpc.id;
export const subnetIds = subnets.map((s) => s.id);Anything exported from the program’s entry point becomes a stack output, readable by StackReference.
Common Mistake: Referencing a stack that’s never been deployed. getOutput returns undefined until the target stack has had at least one successful pulumi up. Run the network stack first.
Fix 5: Resource Renames With Aliases
Renaming a logical name creates a new resource:
// Before:
const bucket = new aws.s3.Bucket("data");
// After (logical rename):
const bucket = new aws.s3.Bucket("data-prod");
// Pulumi sees: delete "data", create "data-prod"For S3 buckets this destroys all your data. Add an alias to preserve the resource identity:
const bucket = new aws.s3.Bucket("data-prod", {
// properties...
}, {
aliases: [{ name: "data" }], // Tells Pulumi this is the same resource
});aliases is in ResourceOptions. Pulumi uses the alias to match against the existing state and updates in place instead of recreating.
For deeper structural renames (parent resource changed, name in a different module):
{
aliases: [
{ name: "data", parent: oldParent },
{ name: "data", type: "aws:s3/bucket:Bucket" },
],
}Pro Tip: When refactoring, do aliases first as a separate pulumi up, verify everything still maps correctly with pulumi state, then remove the alias in a future iteration.
Fix 6: pulumi preview vs pulumi up
Always preview before applying:
pulumi preview # Show what would change
pulumi preview --diff # Show resource-level diffs
pulumi up # Apply
pulumi up --yes # Apply without confirmationPreview catches mistakes before they hit production. The diff shows:
+create-delete~update in place+-replace (delete + create)
Watch for +- on critical resources — replacement of a database, DNS record, or load balancer is downtime.
For untargeted “what about everything?”:
pulumi refresh # Read live state, update Pulumi state to matchRefresh detects drift — resources changed outside of Pulumi (e.g. manual AWS console edits). Run before up if you suspect external changes.
Fix 7: ComponentResource for Abstraction
Group related resources into a reusable component:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
interface AppServiceArgs {
imageUri: pulumi.Input<string>;
envVars?: pulumi.Input<Record<string, string>>;
}
export class AppService extends pulumi.ComponentResource {
public readonly url: pulumi.Output<string>;
constructor(name: string, args: AppServiceArgs, opts?: pulumi.ComponentResourceOptions) {
super("myorg:app:AppService", name, {}, opts);
const task = new aws.ecs.TaskDefinition(`${name}-task`, {
// ... task config ...
}, { parent: this });
const service = new aws.ecs.Service(`${name}-svc`, {
taskDefinition: task.arn,
// ... service config ...
}, { parent: this });
this.url = service.loadBalancer.apply((lb) => lb[0].targetGroupArn);
this.registerOutputs({
url: this.url,
});
}
}
// Usage:
const app = new AppService("my-app", { imageUri: "..." });Three rules for ComponentResources:
- Pass
{ parent: this }on every child resource — keeps the tree structured. - Call
super(...)with a unique type token (myorg:app:AppService). - Call
registerOutputs(...)at the end to expose component-level outputs.
Pro Tip: ComponentResources don’t add to Pulumi Cloud’s resource count for billing — only the underlying cloud resources do. Use them liberally to encapsulate patterns.
Fix 8: Handle Locks and Failures
If pulumi up was interrupted mid-deploy:
error: the stack is currently locked by another updateCheck for a hanging process:
pulumi cancelThis forcibly releases the lock. Only do this if you’re sure no other deployment is running — if you cancel an in-flight deploy, the state may be inconsistent with reality.
For partial failures (some resources created, others failed):
pulumi up # Try again — Pulumi resumes from where it left off
pulumi refresh # If state and reality diverged, refresh firstTo remove a problematic resource from state without deleting it:
pulumi state delete <urn>This is the escape hatch when Pulumi’s view of a resource conflicts with reality (e.g. someone deleted it in the console).
Common Mistake: Running pulumi destroy to clean up “just one resource.” destroy removes everything in the stack. To delete a single resource, remove it from your code and pulumi up.
Still Not Working?
A few less-obvious failures:
error: program exited unexpectedly. Your program threw before completing. Check the line before the error — often a missing required input or unhandled promise rejection.pulumi upshows the same diff on every run. A property’s value depends on a non-deterministic computation (timestamp, random). Either pin it or useignoreChangesinResourceOptions.- Cross-region resources fail to create. Pulumi infers region from provider config. For multi-region, create explicit providers:
new aws.Provider("us-west", { region: "us-west-2" })and pass via{ provider }ResourceOption. Getresources (data sources) return stale. Pulumi cachesGetcalls. Refresh withpulumi refreshor restructure to use the resource directly.- TypeScript build errors after Pulumi upgrade. SDK types changed. Run
pulumi plugin installandnpm installfor the matching versions. Pin@pulumi/pulumiand provider packages inpackage.json. - Stack output not appearing after
up. You need toexportit at module top level:export const url = .... Returning from a function or wrapping in a class doesn’t expose it. Resource still has dependentsduring destroy. Pulumi destroys leaf-first; if a resource has unmanaged dependents (created outside Pulumi), they block deletion. Either delete them manually or import them into Pulumi.- Drift after manual console edits.
pulumi refreshcatches it. To prevent future drift, lock down IAM so only Pulumi can edit managed resources.
For related infrastructure-as-code and deployment issues, see Terraform error acquiring state lock, Terraform failed to install provider, AWS CDK not working, and AWS CloudFormation rollback complete.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS CDK Not Working — Bootstrap Error, ROLLBACK_COMPLETE, and Deploy Failures
How to fix AWS CDK errors — cdk bootstrap required, stack in ROLLBACK_COMPLETE, asset bundling failed, CLI/library version mismatch, VPC lookup failing, and cross-stack export conflicts.
Fix: AWS Amplify Not Working — Gen 2 Backend, defineData, Auth, Storage, and Sandbox Deployments
How to fix AWS Amplify Gen 2 errors — backend.ts file structure, defineData schema authorization, defineAuth flow, defineStorage bucket access, sandbox vs branch deploy, generated outputs, and Cognito triggers.
Fix: AWS Lambda SnapStart Not Working — Version vs Alias, Restore Hooks, and Uniqueness Bugs
How to fix Lambda SnapStart errors — feature requires published version, $LATEST not supported, restore hook for stale connections, UUID collisions after snapshot, time-based state staleness, and pricing surprises.
Fix: Moto Not Working — Mock Decorator, Real AWS Calls Leaking, and v4 to v5 Migration
How to fix Moto errors — mock not activating, real AWS credentials used in tests, ImportError mock_s3 removed in v5, fixtures with multiple services, NoCredentialsError despite mock, and standalone server mode.