Pulumi Best Practices
Load when the user is writing, reviewing, or debugging Pulumi TypeScript/Python programs; asks about Output<T> or apply() usage; wants to create ComponentResource classes; needs to refactor resources without destroying them (aliases); is setting up secrets or config; or is configuring a pulumi preview/up CI workflow. Also load for questions about resource dependency order, parent/child resource relationships, or pulumi.interpolate.
- Skill ID
- pulumi/agent-skills/pulumi-best-practices
- Publisher
- pulumi
- Repository
- agent-skills
- Installs
- 0
- Files
- 3
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
pulumi/agent-skills/pulumi-best-practicesInstalls these files- SKILL.md
- agents/openai.yaml
- use_cases.yaml
What this skill tells the agent
Pulumi Best Practices
When to Use This Skill
Invoke this skill when:
- Writing new Pulumi programs or components
- Reviewing Pulumi code for correctness
- Refactoring existing Pulumi infrastructure
- Debugging resource dependency issues
- Setting up configuration and secrets
Practices
1. Never Create Resources Inside apply()
Why: Resources created inside apply() don't appear in pulumi preview, making changes unpredictable. Pulumi cannot properly track dependencies, leading to race conditions and deployment failures.
Detection signals:
new aws.or other resource constructors inside.apply()callbacks- Resource creation inside
pulumi.all([...]).apply() - Dynamic resource counts determined at runtime inside apply
Wrong:
const bucket = new aws.s3.Bucket("bucket");
bucket.id.apply(bucketId => {
// WRONG: This resource won't appear in preview
new aws.s3.BucketObject("object", {
bucket: bucketId,
content: "hello",
});
});Right:
const bucket = new aws.s3.Bucket("bucket");
// Pass the output directly - Pulumi handles the dependency
const object = new aws.s3.BucketObject("object", {
bucket: bucket.id, // Output<string> works here
content: "hello",
});When apply is appropriate:
- Transforming output values for use in tags, names, or computed strings
- Logging or debugging (not resource creation)
- Conditional logic that affects resource properties, not resource existence
Reference: https://www.pulumi.com/docs/concepts/inputs-outputs/
2. Pass Outputs Directly as Inputs
Why: Pulumi builds a directed acyclic graph (DAG) based on input/output relationships. Passing outputs directly ensures correct creation order. Unwrapping values manually breaks the dependency chain, causing resources to deploy in wrong order or reference values that don't exist yet.
Detection signals:
- Variables extracted from
.apply()used later as resource inputs awaiton output values outside of apply- String concatenation with outputs instead of
pulumi.interpolate
Wrong:
const vpc = new aws.ec2.Vpc("vpc", { cidrBlock: "10.0.0.0/16" });
// WRONG: Extracting the value breaks the dependency chain
let vpcId: string;
vpc.id.apply(id => { vpcId = id; });
const subnet = new aws.ec2.Subnet("subnet", {
vpcId: vpcId, // May be undefined, no tracked dependency
cidrBlock: "10.0.1.0/24",
});Right:
const vpc = new aws.ec2.Vpc("vpc", { cidrBlock: "10.0.0.0/16" });
const subnet = new aws.ec2.Subnet("subnet", {
vpcId: vpc.id, // Pass the Output directly
cidrBlock: "10.0.1.0/24",
});For string interpolation:
// WRONG
const name = bucket.id.apply(id => `prefix-${id}-suffix`);
// RIGHT - use pulumi.interpolate for template literals
const name = pulumi.interpolate`prefix-${bucket.id}-suffix`;
// RIGHT - use pulumi.concat for simple concatenation
const name = pulumi.concat("prefix-", bucket.id, "-suffix");Reference: https://www.pulumi.com/docs/concepts/inputs-outputs/
3. Use Components for Related Resources
Why: ComponentResource classes group related resources into reusable, logical units. Without components, your resource graph is flat, making it hard to understand which resources belong together, reuse patterns across stacks, or reason about your infrastructure at a higher level.
Detection signals:
- Multiple related resources created at top level without grouping
- Repeated resource patterns across stacks that should be abstracted
- Hard to understand resource relationships from the Pulumi console
Wrong:
// Flat structure - no logical grouping, hard to reuse
const bucket = new aws.s3.Bucket("app-bucket");
const bucketPolicy = new aws.s3.BucketPolicy("app-bucket-policy", {
bucket: bucket.id,
policy: policyDoc,
});
const originAccessIdentity = new aws.cloudfront.OriginAccessIdentity("app-oai");
const distribution = new aws.cloudfront.Distribution("app-cdn", { /* ... */ });Right:
interface StaticSiteArgs {
domain: string;
content: pulumi.asset.AssetArchive;
}
class StaticSite extends pulumi.ComponentResource {
public readonly url: pulumi.Output<string>;
constructor(name: string, args: StaticSiteArgs, opts?: pulumi.ComponentResourceOptions) {
super("myorg:components:StaticSite", name, args, opts);
// Resources created here - see practice 4 for parent setup
const bucket = new aws.s3.Bucket(`${name}-bucket`, {}, { parent: this });
// ...
this.url = distribution.domainName;
this.registerOutputs({ url: this.url });
}
}
// Reusable across stacks
const site = new StaticSite("marketing", {
domain: "marketing.example.com",
content: new pulumi.asset.FileArchive("./dist"),
});Component best practices:
- Use a consistent type URN pattern:
organization:module:ComponentName - Call
registerOutputs()at the end of the constructor - Expose outputs as class properties for consumers
- Accept
ComponentResourceOptionsto allow callers to set providers, aliases, etc.
For in-depth component authoring guidance (args design, multi-language support, testing, distribution), use skill pulumi-component.
Reference: https://www.pulumi.com/docs/concepts/resources/components/
4. Always Set parent: this in Components
Why: When you create resources inside a ComponentResource without setting parent: this, those resources appear at the root level of your stack's state. This breaks the logical hierarchy, makes the Pulumi console hard to navigate, and can cause issues with aliases and refactoring. The parent relationship is what makes the component actually group its children.
Detection signals:
- ComponentResource classes that don't pass
{ parent: this }to child resources - Resources inside a component appearing at root level in the console
- Unexpected behavior when adding aliases to components
Wrong:
class MyComponent extends pulumi.ComponentResource {
constructor(name: string, opts?: pulumi.ComponentResourceOptions) {
super("myorg:components:MyComponent", name, {}, opts);
// WRONG: No parent set - this bucket appears at root level
const bucket = new aws.s3.Bucket(`${name}-bucket`);
}
}Right:
class MyComponent extends pulumi.ComponentResource {
constructor(name: string, opts?: pulumi.ComponentResourceOptions) {
super("myorg:components:MyComponent", name, {}, opts);
// RIGHT: Parent establishes hierarchy
const bucket = new aws.s3.Bucket(`${name}-bucket`, {}, {
parent: this
});
const policy = new aws.s3.BucketPolicy(`${name}-policy`, {
bucket: bucket.id,
policy: policyDoc,
}, {
parent: this
});
}
}What parent: this provides:
- Resources appear nested under the component in Pulumi console
- Deleting the component deletes all children
- Aliases on the component automatically apply to children
- Clear ownership in state files
Reference: https://www.pulumi.com/docs/concepts/resources/components/
5. Encrypt Secrets from Day One
Why: Secrets marked with --secret are encrypted in state files, masked in CLI output, and tracked through transformations. Starting with plaintext config and converting later requires credential rotation, reference updates, and audit of leaked values in logs and state history.
Detection signals:
- Passwords, API keys, tokens stored as plain config
- Connection strings with embedded credentials
- Private keys or certificates in plaintext
Wrong:
# Plaintext - will be visible in state and logs
pulumi config set databasePassword hunter2
pulumi config set apiKey sk-1234567890Right:
More from pulumi
Use this skill in a project
Open any RiverX project, search the Skills panel for pulumi/agent-skills/pulumi-best-practices, and install it in one click.
