Sharing Pulumi Outputs Across Stacks: Stack References vs Alternatives
A decision guide for passing infrastructure outputs between Pulumi stacks, comparing Stack References with config, external secrets, and GitOps, plus a TypeScript implementation pattern.
19 Jun 2026, 22:21 UTC

The Problem: Outputs Need to Travel Between Stacks
When a network stack exports a VPC ID and an application stack needs that ID to launch instances, Pulumi teams must decide how to pass the value. The choice determines whether stacks stay loosely coupled or form an implicit deployment chain.
The useful takeaway: use Stack References when both stacks live in the same Pulumi organization and you want type-safe, automatically refreshed values. Choose config, external secret stores, or GitOps when you need to avoid deployment ordering constraints or cross organizational boundaries.
Decision Constraints
Before comparing options, verify these hard constraints:
- Same organization: Stack References resolve only within one Pulumi organization or a single self-hosted backend. Cross-organization references are not supported.
- Deployed producer: The source stack must have been deployed at least once. An undeployed stack has no state to export.
- Backend consistency: Self-hosted backends (S3, Azure Blob, GCS, Pulumi Enterprise) require every stack to point to the same backend URL. Mismatches cause "stack not found" errors.
- Version floor:
requireOutput()andgetOutput()are available since Pulumi 3.0. Older SDKs lack the mandatory-value distinction.
Comparing Supported Options
Four approaches cover most scenarios. The table summarizes the trade-offs.
| Approach | Coupling | Type Safety | Deployment Ordering | Operational Overhead |
|---|---|---|---|---|
| Stack References | Live, implicit | Strong (Output<T>) | Producer must deploy first | Low |
| Config / Secrets | Manual | None | None | Medium (sync burden) |
| External secret stores | Manual | None | None | High (extra infrastructure) |
| GitOps (ArgoCD/Flux) | Git-driven | None | Git merge order | High (repo structure) |
Trade-offs in Practice
Stack References give you strong typing and automatic dependency tracking. Values refresh on pulumi up, so consumers see producer changes without manual copying. The cost is an implicit deployment ordering: if the producer stack has not deployed, the consumer cannot resolve the reference. CI/CD pipelines must sequence stacks explicitly, for example by running pulumi up --stack network-prod before pulumi up --stack app-prod.
A second risk is silent breakage. If you rename or delete an exported output in the producer, consumers will not fail until their next pulumi up. Using requireOutput() for critical values forces a fast failure at preview time instead of a partial apply.
Config-based passing avoids deployment coupling entirely. You copy the VPC ID into the consumer stack's config file. The downside is that the value can drift from reality, and you lose Pulumi's drift detection. There is no type safety—config values arrive as strings or JSON objects.
External secret stores (AWS SSM, Azure Key Vault, HashiCorp Vault) decouple stacks completely but add operational complexity. You must provision and secure the store, manage IAM policies, and accept eventual consistency delays.
GitOps tools shift the coupling into your Git repository. The application manifest references a value committed by the network pipeline. This works well for Kubernetes workloads but makes the Git repo the single source of truth, which can be slower to update than live stack state.
Concrete TypeScript Implementation
The following pattern covers a common multi-environment setup: a shared network stack and an environment-specific application stack.
Producer: Network Stack
In the network project, export the identifiers that other stacks need. Run these commands in the network project directory.
// index.ts (network stack)
// Assume vpc and subnets are created above
export const vpcId = vpc.id;
export const subnetIds = subnets.map(s => s.id);
Deploy the producer so the outputs enter stack state:
pulumi up
Confirm the exports exist:
pulumi stack output --stack myorg/network/network-prod
Expected check: the command lists vpcId and subnetIds with resolved values.
Consumer: Application Stack
In the application project, create a StackReference and consume the outputs. Run these commands in the application project directory.
// index.ts (application stack)
import * as pulumi from "@pulumi/pulumi";
const network = new pulumi.StackReference("myorg/network/network-prod");
// Use requireOutput for values the application cannot run without
const vpcId = network.requireOutput("vpcId");
// Use getOutput for optional values
const subnetIds = network.getOutput("subnetIds");
// Pass vpcId into a resource, e.g. an EC2 instance or ECS service
// const instance = new aws.ec2.Instance("app", { subnetId: vpcId, ... });
Preview the consumer stack to verify resolution:
pulumi preview --stack myorg/app/app-prod
Expected check: the preview plan shows a concrete VPC ID, not an unresolved Output<string> placeholder. If the producer output is missing, requireOutput fails the preview immediately.
Apply the consumer stack:
pulumi up --stack myorg/app/app-prod
Validation and Dependency Visibility
After deployment, verify the linkage in the Pulumi Console. Navigate to the consumer stack, open the Dependencies tab, and confirm the producer stack appears as a linked dependency. This visualization helps audit cross-stack relationships before they become circular.
To check stack lineage from the CLI, run:
pulumi stack ls
Expected check: both producer and consumer stacks appear under the same organization and project naming convention.
Limitations and Scaling Concerns
Stack References are not free. Keep these limits in mind:
- Preview performance: More than roughly 50 cross-stack references can slow
pulumi previewbecause Pulumi fetches each producer stack sequentially. Consolidate exports into a single JSON output object if you approach this threshold. - Circular references: If stack A references stack B and stack B references stack A, Pulumi cannot resolve the dependency. Design a one-way producer-to-consumer hierarchy.
- Free tier ceiling: Pulumi Cloud's free tier limits organizations to 10 stacks. Multi-environment topologies (dev, staging, prod for network, data, and app) can exhaust this quickly.
- Self-hosted backend drift: If one stack uses Pulumi Cloud and another uses an S3 backend, Stack References will not resolve. Standardize the backend URL across all stacks in a project.
Practical Result Check
The simplest way to confirm the pattern works is to break it deliberately. Rename vpcId to vpcIdentifier in the producer, run pulumi preview on the consumer, and verify that requireOutput("vpcId") fails fast. Restore the name, redeploy the producer, and confirm the consumer preview resolves again. This test proves the dependency is live and that missing values are caught before apply.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.