| 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, and configures OIDC authentication for cloud providers. Use when the user asks to create Pulumi TypeScript projects, write Pulumi infrastructure code, set up ESC environments, configure OIDC for Pulumi, or implement infrastructure automation with Node.js/TypeScript. |
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
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