| name | pulumi-typescript |
| description | Scaffolds Pulumi TypeScript infrastructure-as-code projects, writes IaC code with proper resource configuration, manages Pulumi ESC environments for centralized secrets and configuration, configures OIDC authentication for cloud providers, and builds multi-language component resources. Use when the user asks to create Pulumi TypeScript projects, write Pulumi infrastructure code, set up ESC environments, configure OIDC for Pulumi, implement infrastructure automation with Node.js/TypeScript, create reusable Pulumi components, or work with stack references. Also use when the user mentions Pulumi with TypeScript, AWS/Azure/GCP infrastructure in TypeScript, or PulumiPlugin.yaml for multi-language components. |
Pulumi TypeScript Skill
Development Workflow
1. Project Setup
pulumi new typescript
pulumi new aws-typescript
pulumi new azure-typescript
pulumi new gcp-typescript
Project structure:
my-project/
├── Pulumi.yaml
├── Pulumi.dev.yaml # Stack config (use ESC instead)
├── package.json
├── tsconfig.json
└── index.ts
2. Pulumi ESC Integration
Instead of using pulumi config set or stack config files, use Pulumi ESC for centralized secrets and configuration.
Link ESC environment to stack:
pulumi env init myorg/myproject-dev
pulumi env edit myorg/myproject-dev
pulumi config env add myorg/myproject-dev
ESC environment definition (YAML):
values:
pulumiConfig:
aws:region: us-west-2
myapp:instanceType: t3.medium
aws:
login:
fn::open::aws-login:
oidc:
roleArn: arn:aws:iam::123456789:role/pulumi-oidc
sessionName: pulumi-deploy
secrets:
fn::open::aws-secrets:
region: us-west-2
login: ${aws.login}
get:
dbPassword:
secretId: prod/database/password
environmentVariables:
AWS_ACCESS_KEY_ID: ${aws.login.accessKeyId}
AWS_SECRET_ACCESS_KEY: ${aws.login.secretAccessKey}
AWS_SESSION_TOKEN: ${aws.login.sessionToken}
3. TypeScript Patterns
Basic resource creation:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const instanceType = config.require("instanceType");
const bucket = new aws.s3.Bucket("my-bucket", {
versioning: { enabled: true },
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "AES256",
},
},
},
tags: {
Environment: pulumi.getStack(),
ManagedBy: "Pulumi",
},
});
export const bucketName = bucket.id;
export const bucketArn = bucket.arn;
Component resources for reusability:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
interface WebServiceArgs {
port: pulumi.Input<number>;
imageUri: pulumi.Input<string>;
}
class WebService extends pulumi.ComponentResource {
public readonly url: pulumi.Output<string>;
constructor(name: string, args: WebServiceArgs, opts?: pulumi.ComponentResourceOptions) {
super("custom:app:WebService", name, {}, opts);
const lb = new aws.lb.LoadBalancer(`${name}-lb`, {
loadBalancerType: "application",
}, { parent: this });
this.url = lb.dnsName;
this.registerOutputs({ url: this.url });
}
}
Stack references for cross-stack dependencies:
import * as pulumi from "@pulumi/pulumi";
const networkingStack = new pulumi.StackReference("myorg/networking/prod");
const vpcId = networkingStack.getOutput("vpcId");
const subnetIds = networkingStack.getOutput("privateSubnetIds");
Working with Outputs:
import * as pulumi from "@pulumi/pulumi";
const uppercaseName = bucket.id.apply(id => id.toUpperCase());
const combined = pulumi.all([bucket.id, bucket.arn]).apply(
([id, arn]) => `Bucket ${id} has ARN ${arn}`
);
const isProd = pulumi.getStack() === "prod";
const monitoring = isProd ? new aws.cloudwatch.MetricAlarm("alarm", {
}) : undefined;
4. Using ESC with pulumi env run
Run any command with ESC environment variables injected:
pulumi env run myorg/aws-dev -- pulumi up
pulumi env run myorg/test-env -- npm test
pulumi env open myorg/myproject-dev --format shell
5. Async Patterns
export = async () => {
const data = await fetchExternalData();
const resource = new aws.s3.Bucket("bucket", {
tags: { data: data.value },
});
return {
bucketName: resource.id,
};
};
6. Multi-Language Components
Create components in TypeScript that can be consumed from any Pulumi language (Python, Go, C#, Java, YAML).
Project structure for multi-language component:
my-component/
├── PulumiPlugin.yaml # Required for multi-language
├── package.json
├── tsconfig.json
└── index.ts # Component definition
PulumiPlugin.yaml:
runtime: nodejs
Component with proper Args interface:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export interface SecureBucketArgs {
bucketName: pulumi.Input<string>;
enableVersioning?: pulumi.Input<boolean>;
tags?: pulumi.Input<Record<string, pulumi.Input<string>>>;
}
export class SecureBucket extends pulumi.ComponentResource {
public readonly bucketId: pulumi.Output<string>;
public readonly bucketArn: pulumi.Output<string>;
constructor(name: string, args: SecureBucketArgs, opts?: pulumi.ComponentResourceOptions) {
super("myorg:storage:SecureBucket", name, {}, opts);
const bucket = new aws.s3.Bucket(`${name}-bucket`, {
bucket: args.bucketName,
versioning: { enabled: args.enableVersioning ?? true },
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "AES256",
},
},
},
tags: args.tags,
}, { parent: this });
this.bucketId = bucket.id;
this.bucketArn = bucket.arn;
this.registerOutputs({
bucketId: this.bucketId,
bucketArn: this.bucketArn,
});
}
}
Publishing for multi-language consumption:
pulumi package add github.com/myorg/my-component
pulumi package add github.com/myorg/my-component@v1.0.0
pulumi package add /path/to/local/my-component
Multi-language Args requirements:
- Use
pulumi.Input<T> for all scalar properties
- Avoid union types (
string | number) - not supported
- Avoid functions/callbacks - not serializable
- Constructor must have
args parameter with type declaration
Deployment Workflow with Validation
Follow this validated workflow for safe deployments:
1. Preview Changes
pulumi preview
Review the output to understand what resources will be created, modified, or deleted.
2. Validate Changes
Check the preview output for:
- Unexpected resource deletions or modifications
- Correct number and type of resources
- Proper configuration values (region, instance type, tags, etc.)
If changes look incorrect, investigate the root cause before proceeding:
pulumi stack output
pulumi env open myorg/myproject-dev
pulumi config
3. Deploy
Once validated, deploy the changes:
pulumi up
4. Verify Outputs
After successful deployment, confirm the outputs:
pulumi stack output
Compare outputs against expected values (e.g., bucket names, endpoint URLs, resource IDs).
Error Recovery
If pulumi up fails mid-deployment:
- Check the error message for the specific resource that failed
- Review resource configuration and ESC environment values
- Fix the underlying issue (e.g., IAM permissions, invalid configuration)
- Run
pulumi up again — Pulumi will resume from where it left off
- If the stack is in an inconsistent state, use
pulumi refresh to sync state with actual cloud resources
Best Practices
Security
- Use Pulumi ESC for all secrets - never commit secrets to stack config files
- Enable OIDC authentication instead of static credentials
- Use dynamic secrets with short TTLs when possible
- Apply least-privilege IAM policies
- Always enable server-side encryption on S3 buckets (AES256 or KMS) — this is a non-negotiable default
- Block public access on S3 buckets unless explicitly required
- Enable versioning on storage resources for data protection
Code Organization
- Use ComponentResources for reusable infrastructure patterns
- Leverage TypeScript's type system for configuration validation
- Keep stack-specific config in ESC environments
- Use stack references for cross-stack dependencies
Deployment
- Always run
pulumi preview before pulumi up
- Use ESC environment versioning and tags for releases
- Implement proper tagging strategy for all resources
Common Commands
pulumi env init <org>/<project>/<env>
pulumi env edit <org>/<env>
pulumi env open <org>/<env>
pulumi env run <org>/<env> -- <command>
pulumi env version tag <org>/<env> <tag>
pulumi new typescript
pulumi config env add <org>/<env>
pulumi preview
pulumi up
pulumi stack output
pulumi destroy
pulumi refresh
References