Skip to main content
The moment you want to deploy the same infrastructure to more than one environment, hard-coding values stops working. A bucket named my-assets-dev can’t become my-assets-prod without editing the source file — and editing source files per environment is exactly the kind of fragile, error-prone pattern that infrastructure-as-code exists to eliminate. input blocks solve this by turning any value into a configurable parameter with a type, an optional default, and a name you can pass at apply time. On the other side of the same problem, once Pulumi provisions a resource, you need to get its runtime attributes — the endpoint, the ARN, the URL — out of the stack and into the hands of operators, CI pipelines, and other stacks that depend on them. That’s what output blocks are for. Together, inputs and outputs are what turn a collection of resource declarations into a genuinely reusable, composable stack.

What you’ll learn

  • How input blocks declare typed, configurable variables
  • How output blocks export stack values
  • Why inputs are Resolved<T> — available at compile time, not pending
  • How to organise your project into separate .iac files by block type

Why this matters

A stack with well-defined inputs and outputs becomes a reusable building block. The same .iac files can deploy to dev, staging, and prod by passing different values at apply time — no branching, no duplication, no environment-specific source files.

The source code

Splitting your stack into separate files by block type keeps each file focused and easy to navigate. inputs.iac holds your variables, main.iac holds your resources, outputs.iac holds your exports, and ubx.iac holds your project config.
unit "aws_s3_bucket_v2" "assets" {
  # Interpolate input values into strings with ${ }
  bucket = "assets-${input.env}-${input.bucket_suffix}"
}

How it works

1

Inputs are resolved before the stack runs

Inputs with defaults are Resolved<T> — their value is known at compile time. Inputs without defaults are also Resolved<T>, their value passed before the stack runs via --var or Pulumi config. Neither type is Pending<T> — that’s reserved for resource outputs that only exist after Pulumi creates a resource.
2

String interpolation is evaluated at compile time

"assets-${input.env}-${input.bucket_suffix}" resolves into a plain string expression. No .apply() chain is generated — both inputs are resolved values, so the compiler can inline them directly.
3

Outputs are registered with Pulumi

The output blocks compile to export const statements in TypeScript or pulumi.export() in Python/Go. Pulumi registers these as stack outputs visible via ubx output and accessible by other stacks via cross-stack references.

What ubx generates

// Generated by ubx — do not edit
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const env = config.get("env") || "dev";
const bucketSuffix = config.require("bucketSuffix");

const assets = new aws.s3.BucketV2("assets", {
    bucket: `assets-${env}-${bucketSuffix}`,
});

export const bucketName = assets.bucket;
export const bucketArn = assets.arn;

Common mistakes

input.* references are Resolved<T> — they don’t need any special syntax. Only resource outputs like unit.x.y.endpoint are Pending<T> (resolved at apply time).
Use sensitive = true on output blocks for passwords, tokens, or connection strings. ubx wraps the value in pulumi.secret(), encrypting it in state and masking it in ubx output display.

Run it

ubx validate
ubx plan   # requires AWS credentials
ubx apply --var bucket_suffix=myapp   # pass the required input
ubx output # shows bucket_name and bucket_arn after apply

What you learned

input blocks declare typed, configurable variables with optional defaults
Inputs are Resolved<T> — available at compile time, no pending chain required
output blocks export stack values; sensitive = true encrypts them in state
Splitting blocks into main.iac, inputs.iac, and outputs.iac keeps large stacks navigable

Next steps

Locals

Compute reusable values from inputs and other expressions

Pending refs: RDS to Helm

The Pending<T> type system in depth