Connecting the Pieces
Real infrastructure is never a single monolithic stack. You split it into layers: Networking (VPC, subnets, security groups), Data (RDS, ElastiCache), and Application (ECS services, Lambda functions). Each layer is a separate stack, deployed independently. But the Application stack needs the VPC ID from the Networking stack. How do you pass values between stacks?
This is the cross-stack reference problem, and CDK and Pulumi solve it differently.
CDK: Automatic Wiring
In CDK, if both stacks are in the same CDK app, you simply pass the object reference — the app entry point instantiates the networking stack, then instantiates the application stack passing the networking stack's VPC object directly as a prop. CDK figures out the rest. Behind the scenes, CDK:
1. Adds a CloudFormation Export to the Networking stack: Export Name: Networking-VpcId, Value: !Ref VPC
2. Adds an Fn::ImportValue to the Application stack: Fn::ImportValue: Networking-VpcId
You never write this boilerplate. CDK detects the cross-stack dependency from the object reference and generates the correct CloudFormation.
The catch: CloudFormation exports create a hard dependency. You cannot delete or rename the Networking stack while the Application stack is importing its values. This is a real operational constraint — plan your stack boundaries carefully.
Pulumi: Explicit Stack References
Pulumi uses an explicit StackReference object. You name the stack you want to read from, pull a specific output from it by key, and use that value as an input to a new resource. The value returned is a pulumi.Output<string> — a promise-like value that Pulumi resolves at deploy time by reading the networking stack's state. This is explicit and transparent: you can see exactly which stacks depend on which outputs.
The catch: The networking stack must be deployed before the application stack, and the output key ("vpcId") must match exactly. Typos in output names cause runtime errors, not compile-time errors — unless you use TypeScript's type system carefully.
Choosing Between the Approaches
| Concern | CDK | Pulumi |
|---|---|---|
| Ease of use | Automatic — just pass objects | Explicit — must know output names |
| Visibility | Hidden in generated CloudFormation | Explicit in code |
| Cross-account | Requires manual export/import | Supported natively via StackReference |
| Decoupling | Hard dependency via CloudFormation exports | Looser — reads from state at deploy time |
Key Point: CDK's automatic cross-stack wiring is convenient but creates tight CloudFormation-level coupling. Pulumi's explicit
StackReferenceis more verbose but makes dependencies visible in code and supports cross-account scenarios more naturally. For large organizations with many teams, the explicitness of Pulumi's approach often wins — you can grep forStackReferenceto understand the full dependency graph of your infrastructure.