Understanding Inputs
Why Inputs & Outputs Matter
The Problem: Cloud resources depend on each other - a subnet needs a VPC ID, a security group needs a VPC ID, an instance needs both. These values aren't known until resources are actually created.
The Solution: Pulumi's Input/Output type system lets you pass values between resources even before they exist, automatically tracking dependencies and ordering operations correctly.
Real Impact: You write code that looks synchronous, but Pulumi handles all the async complexity of creating resources in the right order behind the scenes.
Real-World Analogy
Think of Inputs and Outputs like a relay race:
- Input<T> = The baton a runner is ready to receive (a value or a promise of a value)
- Output<T> = The baton a runner will hand off after finishing their leg
- .apply() = The handoff zone where you transform the baton
- pulumi.all() = Waiting for multiple runners to finish before starting the next leg
- Dependency Graph = The race schedule determining the order of legs
Input Types
Plain Values
Regular strings, numbers, and booleans that are known at program execution time. These are the simplest inputs.
Output Values
Values produced by other resources that won't be known until the resource is created in the cloud.
Promise Values
Async values from API calls or computations that resolve during the Pulumi program execution.
Input<T> Union
A type that accepts any of the above: T | Output<T> | Promise<T>, giving you maximum flexibility.
import * as aws from "@pulumi/aws";
// Plain value input
const bucket = new aws.s3.Bucket("data", {
acl: "private", // plain string
});
// Output value as input (automatic dependency)
const vpc = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
});
const subnet = new aws.ec2.Subnet("app", {
vpcId: vpc.id, // Output<string> used as Input<string>
cidrBlock: "10.0.1.0/24",
});
Working with Outputs
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("my-bucket");
// bucket.id is Output<string> - not a plain string!
// You CANNOT do: console.log("ID: " + bucket.id)
// Instead, use .apply() to access the value
bucket.id.apply(id => {
console.log(`Bucket ID: ${id}`);
});
// Or use pulumi.interpolate for string concatenation
const bucketUrl = pulumi.interpolate`https://${bucket.bucket}.s3.amazonaws.com`;
// Export outputs for other stacks or CLI access
export const url = bucketUrl;
Output Chaining
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("data");
// Chain .apply() calls to transform outputs
const upperName = bucket.bucket.apply(name =>
name.toUpperCase()
);
// .apply() returns a new Output, so you can chain
const nameLength = bucket.bucket
.apply(name => name.length)
.apply(len => `Name has ${len} characters`);
// Use pulumi.interpolate for cleaner string building
const endpoint = pulumi.interpolate`https://${bucket.bucketRegionalDomainName}/index.html`;
Apply and All
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("main", { cidrBlock: "10.0.0.0/16" });
const subnet = new aws.ec2.Subnet("app", {
vpcId: vpc.id,
cidrBlock: "10.0.1.0/24",
});
// pulumi.all() - combine multiple outputs
const info = pulumi.all([vpc.id, subnet.id]).apply(([vpcId, subnetId]) => {
return `VPC: ${vpcId}, Subnet: ${subnetId}`;
});
// Using output properties directly as inputs
const sg = new aws.ec2.SecurityGroup("web", {
vpcId: vpc.id, // implicit dependency
ingress: [{
protocol: "tcp",
fromPort: 80,
toPort: 80,
cidrBlocks: ["0.0.0.0/0"],
}],
});
// pulumi.all with object syntax
const summary = pulumi.all({
vpcId: vpc.id,
subnetId: subnet.id,
sgId: sg.id,
}).apply(vals => {
return JSON.stringify(vals, null, 2);
});
Dependency Graph
How Dependencies Work
- Implicit: When you pass an Output as an Input, Pulumi automatically creates a dependency
- Explicit: Use
dependsOnresource option for dependencies not captured by data flow - Parallel: Resources without dependencies are created in parallel for faster deployments
- Ordering: Pulumi always creates dependencies before dependents, and deletes in reverse order
Common Pitfall
Problem: Trying to use console.log(bucket.id) prints [Output] instead of the actual value.
Solution: Output values are promises that resolve during deployment. Use .apply() to access the resolved value, or pulumi.interpolate for string interpolation.
Quick Reference
Input/Output Operations
| Operation | Description | Example |
|---|---|---|
.apply(fn) |
Transform an output value | vpc.id.apply(id => id.toUpperCase()) |
pulumi.all() |
Combine multiple outputs | pulumi.all([a.id, b.id]).apply(([a, b]) => ...) |
pulumi.interpolate |
String interpolation with outputs | pulumi.interpolate`arn:${id}` |
pulumi.concat() |
Concatenate outputs and strings | pulumi.concat("prefix-", name) |
pulumi.output() |
Wrap a plain value as Output | pulumi.output("hello") |
export const |
Export stack output | export const url = bucket.websiteEndpoint |