Resources & the program

Declaring infra in code.

Beginner12 min · lesson 3 of 12

A Pulumi program builds infrastructure by constructing resource objects. Each resource takes a name (unique within the program), an arguments object, and optional options; constructing it registers it with the engine as something that should exist. Dependencies are inferred when you pass one resource’s output into another’s input — the same implicit-graph idea as Terraform, expressed as normal variable references.

index.ts
import * as aws from "@pulumi/aws";
const logs = new aws.s3.BucketV2("logs", {
bucket: "acme-logs-prod",
});
// passing logs.id into another resource creates a dependency edge
new aws.s3.BucketVersioningV2("logs-versioning", {
bucket: logs.id,
versioningConfiguration: { status: "Enabled" },
});

Now use the language

This is where Pulumi earns its keep: to make ten buckets, you write a loop; to make a resource conditional, an if; to factor out a pattern, a function. There is no special DSL construct for iteration or conditionals because you already have the language’s. The result is that repetitive or parameterized infrastructure reads like the rest of your codebase.

index.ts
const envs = ["dev", "staging", "prod"];
const buckets = envs.map(env =>
new aws.s3.BucketV2(`data-${env}`, { bucket: `acme-data-${env}` })
);
// a helper function that encapsulates a pattern:
function privateBucket(name: string) {
const b = new aws.s3.BucketV2(name, {});
new aws.s3.BucketPublicAccessBlock(`${name}-block`, {
bucket: b.id, blockPublicAcls: true, blockPublicPolicy: true,
});
return b;
}
Resource names must be stable
Pulumi tracks a resource by its logical name (the first argument). Change that name and Pulumi sees the old resource deleted and a new one created — a destroy/replace, not a rename. When you refactor, keep logical names stable (or use aliases) so a cosmetic code change does not tear down and rebuild live infrastructure.