| name | pulumi-components |
| user-invocable | false |
| description | Use when building reusable infrastructure components with Pulumi for modular, composable cloud resources. |
| allowed-tools | ["Bash","Read"] |
Pulumi Components
Build reusable infrastructure components with Pulumi to create modular, composable, and maintainable infrastructure.
Overview
Pulumi ComponentResources allow you to create higher-level abstractions that encapsulate multiple cloud resources into logical units. This enables code reuse, better organization, and more maintainable infrastructure code.
Basic ComponentResource
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export interface WebServerArgs {
instanceType?: pulumi.Input<string>;
ami?: pulumi.Input<string>;
subnetId: pulumi.Input<string>;
vpcId: pulumi.Input<string>;
}
export class WebServer extends pulumi.ComponentResource {
public readonly instance: aws.ec2.Instance;
public readonly securityGroup: aws.ec2.SecurityGroup;
public readonly publicIp: pulumi.Output<string>;
constructor(name: string, args: WebServerArgs, opts?: pulumi.ComponentResourceOptions) {
super("custom:infrastructure:WebServer", name, {}, opts);
const defaultOpts = { parent: this };
this.securityGroup = new aws.ec2.SecurityGroup(`${name}-sg`, {
vpcId: args.vpcId,
description: "Security group for web server",
ingress: [
{
protocol: "tcp",
fromPort: 80,
toPort: 80,
cidrBlocks: ["0.0.0.0/0"],
},
{
protocol: "tcp",
fromPort: 443,
toPort: 443,
cidrBlocks: ["0.0.0.0/0"],
},
],
egress: [{
protocol: "-1",
fromPort: 0,
toPort: 0,
cidrBlocks: ["0.0.0.0/0"],
}],
tags: {
Name: `${name}-sg`,
},
}, defaultOpts);
this.instance = new aws.ec2.Instance(`${name}-instance`, {
instanceType: args.instanceType || "t3.micro",
ami: args.ami,
subnetId: args.subnetId,
vpcSecurityGroupIds: [this.securityGroup.id],
tags: {
Name: `${name}-instance`,
},
}, defaultOpts);
this.publicIp = this.instance.publicIp;
this.registerOutputs({
instance: this.instance,
securityGroup: this.securityGroup,
publicIp: this.publicIp,
});
}
}
Advanced VPC Component
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export interface VpcNetworkArgs {
cidrBlock?: string;
availabilityZones?: string[];
enableNatGateway?: boolean;
enableVpnGateway?: boolean;
enableDnsHostnames?: boolean;
enableDnsSupport?: boolean;
privateSubnetCidrs?: string[];
publicSubnetCidrs?: string[];
tags?: { [key: string]: string };
}
export class VpcNetwork extends pulumi.ComponentResource {
public readonly vpc: aws.ec2.Vpc;
public readonly publicSubnets: aws.ec2.Subnet[];
public readonly privateSubnets: aws.ec2.Subnet[];
public readonly : aws..;
?: aws..[];
: aws..;
: aws..[];
: pulumi.<>;
() {
(, name, {}, opts);
defaultOpts = { : };
cidrBlock = args. || ;
azs = args. || [, ];
publicCidrs = args. || [, ];
privateCidrs = args. || [, ];
. = aws..(, {
: cidrBlock,
: args. !== ,
: args. !== ,
: {
: ,
...args.,
},
}, defaultOpts);
. = ..;
. = aws..(, {
: ..,
: {
: ,
...args.,
},
}, defaultOpts);
. = [];
( i = ; i < azs.; i++) {
subnet = aws..(, {
: ..,
: publicCidrs[i],
: azs[i],
: ,
: {
: ,
: ,
...args.,
},
}, defaultOpts);
..(subnet);
}
. = [];
( i = ; i < azs.; i++) {
subnet = aws..(, {
: ..,
: privateCidrs[i],
: azs[i],
: {
: ,
: ,
...args.,
},
}, defaultOpts);
..(subnet);
}
. = aws..(, {
: ..,
: {
: ,
...args.,
},
}, defaultOpts);
aws..(, {
: ..,
: ,
: ..,
}, defaultOpts);
..( {
aws..(, {
: subnet.,
: ..,
}, defaultOpts);
});
(args. !== ) {
. = [];
..( {
eip = aws..(, {
: ,
: {
: ,
...args.,
},
}, defaultOpts);
natGw = aws..(, {
: subnet.,
: eip.,
: {
: ,
...args.,
},
}, defaultOpts);
..(natGw);
});
}
. = [];
..( {
rt = aws..(, {
: ..,
: {
: ,
...args.,
},
}, defaultOpts);
(. && .[i]) {
aws..(, {
: rt.,
: ,
: .[i].,
}, defaultOpts);
}
aws..(, {
: subnet.,
: rt.,
}, defaultOpts);
..(rt);
});
.({
: .,
: .,
: .,
: .,
: .,
: .,
});
}
}
Database Component with RDS
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export interface DatabaseArgs {
engine: "postgres" | "mysql" | "mariadb";
engineVersion: string;
instanceClass?: string;
allocatedStorage?: number;
databaseName: string;
username: string;
password: pulumi.Input<string>;
vpcId: pulumi.Input<string>;
subnetIds: pulumi.Input<string>[];
backupRetentionPeriod?: number;
multiAz?: boolean;
allowedSecurityGroupIds?: pulumi.Input<string>[];
allowedCidrBlocks?: string[];
}
export class Database extends pulumi.ComponentResource {
public readonly instance: aws.rds.Instance;
: aws..;
: aws..;
: pulumi.<>;
: pulumi.<>;
() {
(, name, {}, opts);
defaultOpts = { : };
. = aws..(, {
: args.,
: {
: ,
},
}, defaultOpts);
. = aws..(, {
: args.,
: ,
: {
: ,
},
}, defaultOpts);
portMap = {
: ,
: ,
: ,
};
dbPort = portMap[args.];
(args.) {
args..( {
aws..(, {
: ,
: dbPort,
: dbPort,
: ,
: sgId,
: ..,
}, defaultOpts);
});
}
(args.) {
aws..(, {
: ,
: dbPort,
: dbPort,
: ,
: args.,
: ..,
}, defaultOpts);
}
. = aws..(, {
: args.,
: args.,
: args. || ,
: args. || ,
: args.,
: args.,
: args.,
: ..,
: [..],
: args. || ,
: args. || ,
: ,
: {
: ,
},
}, defaultOpts);
. = ..;
. = ..;
.({
: .,
: .,
: .,
});
}
}
Container Application Component (ECS)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export interface ContainerAppArgs {
vpcId: pulumi.Input<string>;
publicSubnetIds: pulumi.Input<string>[];
privateSubnetIds: pulumi.Input<string>[];
containerImage: string;
containerPort: number;
cpu?: number;
memory?: number;
desiredCount?: number;
environment?: { [key: string]: string };
secrets?: { [key: string]: pulumi.Input<string> };
}
export class ContainerApp extends pulumi.ComponentResource {
public readonly cluster: aws.ecs.Cluster;
public readonly taskDefinition: aws.ecs.;
: aws..;
: aws..;
: aws..;
: pulumi.<>;
() {
(, name, {}, opts);
defaultOpts = { : };
. = aws..(, {
: {
: ,
},
}, defaultOpts);
albSg = aws..(, {
: args.,
: ,
: [
{
: ,
: ,
: ,
: [],
},
{
: ,
: ,
: ,
: [],
},
],
: [{
: ,
: ,
: ,
: [],
}],
: {
: ,
},
}, defaultOpts);
. = aws..(, {
: ,
: ,
: [albSg.],
: args.,
: {
: ,
},
}, defaultOpts);
. = aws..(, {
: args.,
: ,
: args.,
: ,
: {
: ,
: ,
: ,
: ,
: ,
: ,
},
: {
: ,
},
}, defaultOpts);
aws..(, {
: ..,
: ,
: ,
: [{
: ,
: ..,
}],
}, defaultOpts);
taskExecRole = aws..(, {
: aws..({
: ,
}),
: {
: ,
},
}, defaultOpts);
aws..(, {
: taskExecRole.,
: ,
}, defaultOpts);
envVars = .(args. || {}).( ({
name,
value,
}));
secretVars = .(args. || {}).( ({
name,
valueFrom,
}));
containerDef = pulumi.([
pulumi.(args.),
pulumi.(args.),
]).( .([{
: ,
: image,
: args. || ,
: args. || ,
: ,
: [{
: port,
: ,
}],
: envVars,
: secretVars. > ? secretVars : ,
: {
: ,
: {
: ,
: aws.().( r.),
: ,
},
},
}]));
aws..(, {
: ,
: ,
: {
: ,
},
}, defaultOpts);
. = aws..(, {
: name,
: (args. || ),
: (args. || ),
: ,
: [],
: taskExecRole.,
: containerDef,
: {
: ,
},
}, defaultOpts);
serviceSg = aws..(, {
: args.,
: ,
: [{
: ,
: args.,
: args.,
: [albSg.],
}],
: [{
: ,
: ,
: ,
: [],
}],
: {
: ,
},
}, defaultOpts);
. = aws..(, {
: ..,
: ..,
: args. || ,
: ,
: {
: args.,
: [serviceSg.],
: ,
},
: [{
: ..,
: ,
: args.,
}],
: {
: ,
},
}, defaultOpts);
. = ..;
.({
: .,
: .,
: .,
});
}
}
S3 Static Website Component
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export interface StaticWebsiteArgs {
domainName?: string;
indexDocument?: string;
errorDocument?: string;
enableCdn?: boolean;
certificateArn?: pulumi.Input<string>;
}
export class StaticWebsite extends pulumi.ComponentResource {
public readonly bucket: aws.s3.Bucket;
public readonly bucketPolicy: aws.s3.BucketPolicy;
public readonly distribution?: aws.cloudfront.Distribution;
public readonly websiteUrl: pulumi.Output<string>;
constructor(name: string, args: StaticWebsiteArgs, ?: pulumi.) {
(, name, {}, opts);
defaultOpts = { : };
. = aws..(, {
: args. || ,
: {
: args. || ,
: args. || ,
},
: {
: ,
},
}, defaultOpts);
aws..(, {
: ..,
: args. !== ,
: args. !== ,
: args. !== ,
: args. !== ,
}, defaultOpts);
(args. !== ) {
oai = aws..(, {
: ,
}, defaultOpts);
. = aws..(, {
: ..,
: pulumi.([.., oai.]).(
.({
: ,
: [{
: ,
: {
: oaiArn,
},
: ,
: ,
}],
})
),
}, defaultOpts);
. = aws..(, {
: ,
: args. || ,
: [{
: ,
: ..,
: {
: oai.,
},
}],
: {
: ,
: ,
: [, , ],
: [, ],
: ,
: {
: ,
: {
: ,
},
},
: ,
: ,
: ,
},
: {
: {
: ,
},
},
: args. ? {
: args.,
: ,
: ,
} : {
: ,
},
: args. ? [args.] : ,
: [{
: ,
: ,
: ,
}],
: {
: ,
},
}, defaultOpts);
. = ...( );
} {
. = aws..(, {
: ..,
: ...(
.({
: ,
: [{
: ,
: ,
: ,
: ,
}],
})
),
}, defaultOpts);
. = ...( );
}
.({
: .,
: .,
: .,
});
}
}
Kubernetes Application Component
import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";
export interface K8sAppArgs {
namespace?: string;
image: string;
replicas?: number;
port: number;
resources?: {
requests?: {
memory?: string;
cpu?: string;
};
limits?: {
memory?: string;
cpu?: string;
};
};
environment?: { [key: string]: string };
secrets?: { [key: string]: string };
enableIngress?: boolean;
ingressHost?: string;
}
export class K8sApp extends pulumi.ComponentResource {
public readonly namespace: k8s.core.v1.Namespace;
public : k8s...;
: k8s...;
?: k8s...;
?: k8s...;
?: k8s...;
() {
(, name, {}, opts);
defaultOpts = { : };
ns = args. || ;
(args. && args. !== ) {
. = k8s...(, {
: {
: args.,
},
}, defaultOpts);
}
(args. && .(args.). > ) {
. = k8s...(, {
: {
: ,
: ns,
},
: args.,
}, defaultOpts);
}
(args. && .(args.). > ) {
. = k8s...(, {
: {
: ,
: ns,
},
: args.,
}, defaultOpts);
}
: [] = [];
(.) {
.(args. || {}).( {
envVars.({
: key,
: {
: {
: ,
: key,
},
},
});
});
}
(.) {
.(args. || {}).( {
envVars.({
: key,
: {
: {
: ,
: key,
},
},
});
});
}
. = k8s...(, {
: {
: ,
: ns,
: {
: name,
},
},
: {
: args. || ,
: {
: {
: name,
},
},
: {
: {
: {
: name,
},
},
: {
: [{
: name,
: args.,
: [{
: args.,
}],
: envVars. > ? envVars : ,
: args.,
: {
: {
: ,
: args.,
},
: ,
: ,
},
: {
: {
: ,
: args.,
},
: ,
: ,
},
}],
},
},
},
}, defaultOpts);
. = k8s...(, {
: {
: ,
: ns,
: {
: name,
},
},
: {
: {
: name,
},
: [{
: ,
: args.,
: ,
}],
: args. ? : ,
},
}, defaultOpts);
(args. && args.) {
. = k8s...(, {
: {
: ,
: ns,
: {
: ,
: ,
},
},
: {
: [{
: [args.],
: ,
}],
: [{
: args.,
: {
: [{
: ,
: ,
: {
: {
: ,
: {
: ,
},
},
},
}],
},
}],
},
}, defaultOpts);
}
.({
: .,
: .,
: .,
});
}
}
Lambda Function Component
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export interface LambdaFunctionArgs {
runtime: aws.lambda.Runtime;
handler: string;
code: pulumi.asset.AssetArchive | pulumi.asset.FileArchive;
environment?: { [key: string]: string };
timeout?: number;
memorySize?: number;
vpcConfig?: {
subnetIds: pulumi.Input<string>[];
securityGroupIds: pulumi.Input<string>[];
};
policies?: pulumi.Input<string>[];
layers?: pulumi.Input<string>[];
}
export class LambdaFunction extends pulumi.ComponentResource {
public readonly function: aws.lambda.Function;
: aws..;
: aws..;
: pulumi.<>;
() {
(, name, {}, opts);
defaultOpts = { : };
. = aws..(, {
: aws..({
: ,
}),
: {
: ,
},
}, defaultOpts);
aws..(, {
: ..,
: ,
}, defaultOpts);
(args.) {
aws..(, {
: ..,
: ,
}, defaultOpts);
}
(args.) {
args..( {
aws..(, {
: ..,
: policyArn,
}, defaultOpts);
});
}
. = aws..(, {
: ,
: ,
: {
: ,
},
}, defaultOpts);
. = aws..(, {
: args.,
: args.,
: args.,
: ..,
: args. || ,
: args. || ,
: args. ? {
: args.,
} : ,
: args.,
: args.,
: {
: ,
},
}, defaultOpts);
. = ..;
.({
: .,
: .,
});
}
}
When to Use This Skill
Use the pulumi-components skill when you need to:
- Create reusable infrastructure abstractions
- Encapsulate multiple resources into logical units
- Build infrastructure libraries for your organization
- Implement complex multi-resource patterns
- Ensure consistent resource configurations
- Create higher-level infrastructure APIs
- Share infrastructure code across projects
- Build opinionated infrastructure templates
- Manage resource relationships and dependencies
- Create self-contained infrastructure modules
Best Practices
- Use Parent Relationships: Always set
{ parent: this } when creating child resources to maintain proper resource hierarchy
- Register Outputs: Call
registerOutputs() at the end of constructor to expose component properties
- Type Safety: Use TypeScript interfaces for component arguments with clear types
- Input Types: Use
pulumi.Input<T> for arguments that can be outputs from other resources
- Naming Convention: Prefix child resource names with the component name for clarity
- Default Options: Create a
defaultOpts object with parent set for all child resources
- Documentation: Add JSDoc comments explaining component purpose and usage
- Composition Over Inheritance: Favor creating components that compose other components
- Single Responsibility: Each component should encapsulate a single logical infrastructure unit
- Explicit Dependencies: Don't rely on implicit dependencies; make them explicit in code
- Resource Groups: Use tags consistently across all resources in a component
- Error Handling: Validate inputs in the constructor before creating resources
- Immutability: Avoid modifying component state after construction
- Export Typed Outputs: Export strongly-typed outputs for use by consumers
- Provider Configuration: Allow provider configuration to be passed through opts
Common Pitfalls
- Missing Parent: Forgetting to set
parent: this breaks resource hierarchy and prevents proper deletion
- Not Registering Outputs: Forgetting
registerOutputs() prevents output tracking
- Incorrect Type URN: Using wrong format for component type (should be
category:subcategory:Name)
- Circular Dependencies: Creating circular references between components
- Improper Output Handling: Not using
pulumi.Output.apply() for dependent values
- Hardcoded Values: Hardcoding values that should be configurable arguments
- Missing Resource Names: Not prefixing child resource names can cause name conflicts
- Inconsistent Tagging: Not applying consistent tags across all component resources
- Overly Complex Components: Creating components that do too much
- Poor Abstraction Level: Creating components at wrong abstraction level (too high or too low)
- Missing Validation: Not validating required arguments before resource creation
- State Mutations: Mutating component state after construction
- Implicit Dependencies: Relying on Pulumi to figure out dependencies instead of being explicit
- Missing Error Messages: Not providing helpful error messages for invalid configurations
- Tight Coupling: Creating components that are too tightly coupled to specific implementations
Resources