Skip to content

Fix: Pulumi Not Working — Output<T>, Stack References, Secrets, State Backend, and Preview vs Up

FixDevs ·

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_FILE

Or a deploy partially succeeds and you can’t redeploy:

error: failed to register new resource ... -- conflicting resource registrations

Why This Happens

Pulumi differs from Terraform in important ways:

  • Output<T> is async. Pulumi resource properties return Output<T>, not the raw value. They resolve during pulumi up after dependencies create. You can chain with .apply(), interpolate with pulumi.interpolate, but you can’t directly read them in your TypeScript flow.
  • Stack references read another stack’s outputs. They work — but getOutput returns Output<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 file

For non-Cloud backends, Pulumi encrypts secrets with a passphrase. Set:

export PULUMI_CONFIG_PASSPHRASE=your-secret-passphrase
# Or:
export PULUMI_CONFIG_PASSPHRASE_FILE=~/.pulumi-passphrase

For 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 --yes

Pro 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 confirmation

Preview 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 match

Refresh 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:

  1. Pass { parent: this } on every child resource — keeps the tree structured.
  2. Call super(...) with a unique type token (myorg:app:AppService).
  3. 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 update

Check for a hanging process:

pulumi cancel

This 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 first

To 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 up shows the same diff on every run. A property’s value depends on a non-deterministic computation (timestamp, random). Either pin it or use ignoreChanges in ResourceOptions.
  • 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.
  • Get resources (data sources) return stale. Pulumi caches Get calls. Refresh with pulumi refresh or restructure to use the resource directly.
  • TypeScript build errors after Pulumi upgrade. SDK types changed. Run pulumi plugin install and npm install for the matching versions. Pin @pulumi/pulumi and provider packages in package.json.
  • Stack output not appearing after up. You need to export it at module top level: export const url = .... Returning from a function or wrapping in a class doesn’t expose it.
  • Resource still has dependents during 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 refresh catches 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles