skip to content

When one Pulumi stack needs a value produced by another — say an app stack needing a VPC id from a networking stack — how do you wire that up, and what are the failure modes?

level: middleimportance: should knowfreq 45%

answer

  1. publish and subscribe, not a shared graph
  2. only exported values are visible
  3. fully qualified org/project/stack name
  4. reads the last successful deployment
  5. permissions and ordering are yours

basics

~20 s

The producer stack exports the value as a stack output; the consumer constructs a pulumi.StackReference to the producer's fully qualified name and reads it with getOutput. The consumer needs read access to the producer's stack in the backend, and deployment order between the two stacks is your responsibility, not the engine's.

solid answer

~50 s

You publish and subscribe. In the networking program you export the value — in TypeScript that's a top-level `export const vpcId = vpc.id`, in Python `pulumi.export("vpcId", vpc.id)`. In the app program you create `new pulumi.StackReference("acme/networking/prod")` and read `ref.getOutput("vpcId")`, which gives you an `Output` you can pass straight into resource arguments; `requireOutput` is the variant that fails loudly when the key is missing instead of handing back undefined. Two things to be explicit about in an interview. First, permissions: the consumer's credentials must be able to read the producer's stack in the backend — a Pulumi Cloud stack-read grant, or object read on the self-managed bucket. Second, ordering: a stack reference reads the outputs of the producer's **last successful deployment**. There is no cross-stack dependency graph, so your pipeline has to deploy networking before app, and a renamed export breaks the consumer on its next run.

code

typescript · 13 lines
typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Consumer stack: read the networking stack's published outputs.
const net = new pulumi.StackReference("acme/networking/prod");
const vpcId = net.requireOutput("vpcId") as pulumi.Output<string>;

const sg = new aws.ec2.SecurityGroup("app-sg", {
    vpcId: vpcId,
    ingress: [{ protocol: "tcp", fromPort: 443, toPort: 443, cidrBlocks: ["0.0.0.0/0"] }],
});

export const securityGroupId = sg.id;

go deeper

for a junior

Know that a stack publishes values by exporting them and another stack reads them with pulumi.StackReference plus getOutput, using the full org/project/stack name. Say that only exported values are visible.

for a middle

Explain that the reference returns an Output, that requireOutput fails loudly on a missing key, and that the value read is whatever the producer published on its last successful update — there is no cross-stack graph.

for a senior

Talk through operating it: pipeline ordering between producer and consumer, the read permissions the consumer's CI identity needs on the producer's stack, and how you handle renaming or retiring an exported value without breaking downstream stacks.

for a principal

Own the coupling itself. Decide where stack boundaries fall so cross-stack references stay few and stable, when a convention-based data-source lookup is the better contract, and how a change to a shared output is rolled out across teams that deploy on their own schedules.

## The producer side A stack only publishes what its program explicitly exports. In TypeScript that is a module-level export: ```typescript const vpc = new aws.ec2.Vpc("main", { cidrBlock: "10.0.0.0/16" }); export const vpcId = vpc.id; export const privateSubnetIds = privateSubnets.map(s => s.id); ``` In Python the equivalent is `pulumi.export("vpcId", vpc.id)`. After `pulumi up`, `pulumi stack output` lists them, and they are recorded with the stack — which is why the consumer can read them without running the producer's code. ## The consumer side ```typescript const net = new pulumi.StackReference("acme/networking/prod"); const vpcId = net.requireOutput("vpcId"); const sg = new aws.ec2.SecurityGroup("app", { vpcId: vpcId as pulumi.Output<string>, }); ``` The name is the **fully qualified** `<org>/<project>/<stack>`. On a self-managed backend the organization segment is literally `organization`. `getOutput` returns an `Output` that resolves to `undefined` when the key doesn't exist; `requireOutput` fails the update instead. Prefer `requireOutput` — a silently undefined VPC id turns into a confusing provider error several resources later rather than a clear message. Because the result is an `Output`, it composes normally: pass it into resource arguments, interpolate it, or transform it in an `apply`. You do not get a plain string synchronously. ## Failure mode 1: permissions A stack reference is a *read of another stack's recorded outputs*, so the identity running the consumer must be allowed to perform that read. On Pulumi Cloud that is a stack-level read permission in the organization; on a self-managed backend it is read access to the producer's objects in the bucket, and you must be logged into the same backend. This bites in CI, where the app pipeline's credentials were scoped tightly and never granted the networking stack. The symptom is an authorization failure at reference construction, not at resource creation. ## Failure mode 2: ordering is yours Within one stack, Pulumi builds a dependency graph and orders everything for you. Across stacks it does not. A stack reference returns whatever the producer published on its last successful `up`. If you change the VPC in networking and deploy the app first, the app happily builds against the old value. The fix is pipeline discipline: deploy producers before consumers, and treat an output as an interface you version and change carefully. ## Failure mode 3: the interface drifts Exports are string keys. Rename `vpcId` to `vpcID` in the producer and every consumer breaks on its next run — with `getOutput`, silently; with `requireOutput`, loudly. Treat the set of exports as the stack's public API: add before you remove, keep the old key exported for a release if consumers are deployed on their own schedule, and grep for consumers before renaming. ## Failure mode 4: destroying a producer `pulumi destroy` on the networking stack does not warn that another stack references it. The consumer's next update fails, or worse succeeds against resources that no longer exist. Cross-stack coupling has to be documented somewhere a human reads, because the tool will not tell you. ## Secrets across stacks If the producer exported a secret, reading it still requires the consumer to be able to decrypt it, which means the reference is subject to both stack-read permission and the producer's secrets provider. Don't design around passing credentials between stacks if a secret store can hand both stacks the same value directly. ## The alternative worth mentioning A stack reference is not the only way to get a VPC id. You can also look the resource up with a provider data source — query by tag or name — which decouples you from whether the producer is even a Pulumi stack, at the cost of relying on a naming or tagging convention. Stack references are stronger and more explicit; data-source lookups are looser and survive the producer being someone else's tool. Choose based on who owns the producer and how stable the convention is.

  • What guarantees the networking stack is deployed before the app stack that references it?
    Nothing in Pulumi. The engine's dependency graph stops at the stack boundary, and a `StackReference` simply reads whatever the producer published on its last successful update. Ordering is a pipeline concern: deploy producers first, and if a consumer must not run against a stale value, gate it on the producer's deployment in CI.
  • When would you use a provider data-source lookup instead of a StackReference?
    When the producer isn't a Pulumi stack you control, or when you deliberately want loose coupling — looking a VPC up by tag works regardless of what created it. The tradeoff is that you depend on a naming or tagging convention rather than an explicit interface, and a convention drift fails at apply time with a vaguer error.
  • How should you treat the set of values a stack exports?
    As a public API. Consumers reference exports by string key, so renaming or dropping one breaks them on their next run with no compile-time signal. Add new keys before removing old ones, keep a deprecated key exported through at least one consumer release, and prefer `requireOutput` on the consumer side so a missing key fails immediately.

saying these in an interview costs you the question

  • Thinks Pulumi orders stacks automatically by their references
  • Expects getOutput to return a plain string synchronously
  • Assumes any value in the producer is readable, not just exports
  • Forgets the consumer needs read access to the producer's stack
  • Renames an export without checking who consumes it

context