Using Pulumi With TypeScript

A Complete Guide to Modern Infrastructure as Code

Infrastructure as code has transformed how teams deploy and manage cloud resources. While traditional tools like Terraform and CloudFormation use domain-specific languages, Pulumi stands apart by letting you use general-purpose programming languages like TypeScript. This approach brings familiar tooling, strong typing, and the full power of your existing development workflow to infrastructure management.

In this guide, you'll discover how Pulumi with TypeScript enables teams to define cloud infrastructure using the same language and patterns they already know. Our web development services team leverages these modern approaches to build scalable, maintainable infrastructure for client projects. We'll cover setup, resource creation, best practices, and strategies for organizing and testing your infrastructure code.

Why TypeScript for Infrastructure as Code

Key advantages that make TypeScript a preferred choice for modern teams

Strong Typing

Catches configuration errors at development time rather than deployment time. TypeScript's compiler verifies that resource properties follow cloud provider conventions before you deploy.

IDE Support

Full autocomplete, inline documentation, and refactoring capabilities. Your IDE becomes a powerful tool for infrastructure code, suggesting properties and catching mistakes as you type.

Familiar Syntax

Application developers can contribute to infrastructure without learning a DSL. Since TypeScript looks like application code, the learning curve is minimal.

Rich Ecosystem

Leverage the npm package ecosystem for reusable components, shared types, and infrastructure patterns that other teams have already built.

Setting Up Your Pulumi TypeScript Project

Prerequisites

Before you begin, ensure you have the following installed:

  • Node.js - Current, active, or maintenance LTS versions recommended
  • Pulumi CLI - Available for all major operating systems
  • A cloud provider account - AWS, Azure, or GCP with appropriate credentials

Creating a New Pulumi TypeScript Project

The fastest way to start is using the Pulumi CLI's built-in templates:

pulumi new typescript

This command creates a new directory with:

  • package.json - Node.js dependencies including @pulumi/pulumi and provider packages
  • tsconfig.json - TypeScript configuration for your project
  • Pulumi.yaml - Project configuration file
  • index.ts - Your main infrastructure code file
  • Pulumi.<stack>.yaml - Stack configuration files

Understanding the Project Structure

A typical Pulumi TypeScript project organizes code around stacks, resources, and configuration. The main entry point exports stack outputs that other stacks or applications can reference.

// index.ts - main entrypoint
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Create an S3 bucket
const bucket = new aws.s3.Bucket("my-bucket", {
 bucket: "my-unique-bucket-name",
 versioning: { enabled: true },
 serverSideEncryptionConfiguration: {
 rule: {
 applyServerSideEncryptionByDefault: {
 sseAlgorithm: "AES256",
 },
 },
 },
});

// Export the bucket name
export const bucketName = bucket.id;
Creating AWS Resources with TypeScript
1import * as aws from "@pulumi/aws";2 3// Create a VPC4const vpc = new aws.ec2.Vpc("main-vpc", {5 cidrBlock: "10.0.0.0/16",6 enableDnsHostnames: true,7 enableDnsSupport: true,8 tags: {9 Name: "main-vpc",10 Environment: pulumi.getStack(),11 },12});13 14// Create subnets15const publicSubnet = new aws.ec2.Subnet("public-subnet", {16 vpcId: vpc.id,17 cidrBlock: "10.0.1.0/24",18 availabilityZone: "us-east-1a",19 mapPublicIpOnLaunch: true,20 tags: {21 Name: "public-subnet",22 },23});

Understanding Input and Output Types

A fundamental concept in Pulumi is the distinction between inputs and outputs. Input properties are values you provide when creating resources, while output properties are values that the cloud provider generates. Pulumi's type system tracks these relationships automatically.

import * as aws from "@pulumi/aws";

// The VPC ID is an Output<string>, which can be used as input to other resources
const vpc = new aws.ec2.Vpc("main-vpc", {
 cidrBlock: "10.0.0.0/16",
});

// Subnet automatically receives the VPC ID as an Input
const subnet = new aws.ec2.Subnet("subnet", {
 vpcId: vpc.id, // Pulumi tracks this dependency
 cidrBlock: "10.0.1.0/24",
});

// You can transform outputs using apply() when you need the actual value
vpc.id.apply(id => {
 console.log(`VPC created with ID: ${id}`);
});

The type system ensures that dependencies are properly tracked, so Pulumi knows to create the VPC before the subnet. This eliminates an entire class of ordering bugs that can occur with traditional IaC tools.

Organizing Pulumi Projects for Scale

As your infrastructure grows, organizing code becomes critical. Pulumi's best practices for code organization and stack management recommend structuring projects around logical boundaries.

Recommended Project Structure

my-infrastructure/
├── Pulumi.yaml # Project configuration
├── Pulumi.dev.yaml # Dev stack configuration
├── Pulumi.prod.yaml # Prod stack configuration
├── index.ts # Main entrypoint
├── networking/ # Networking resources
│ ├── vpc.ts
│ └── subnets.ts
├── compute/ # Compute resources
│ ├── ec2.ts
│ └── lambda.ts
├── storage/ # Storage resources
│ ├── s3.ts
│ └── rds.ts
└── services/ # Application services
 └── ecs.ts

When to Use Separate Projects

Consider separate Pulumi projects when:

  • Different teams own different infrastructure components
  • You need different deployment pipelines or schedules
  • Infrastructure resources have very different lifecycles
  • You want to limit blast radius of changes

Stack Organization Strategies

Stacks represent isolated instances of your infrastructure within a project. Common patterns include:

  • Environment-based: dev, staging, production
  • Region-based: us-east-1, eu-west-1, ap-southeast-1
  • Feature-based: shared-services, application-a, application-b

Configuration and Secrets Management

Environment-Specific Configuration

Pulumi's configuration system allows you to define environment-specific values without code changes:

# Set configuration for a stack
pulumi config set instanceType t3.micro --stack dev
pulumi config set instanceType t3.large --stack prod
import * as pulumi from "@pulumi/pulumi";

const config = new pulumi.Config();

// Get configuration with type safety
const instanceType = config.get("instanceType") || "t3.micro";
const instanceCount = config.getNumber("instanceCount") || 2;

Managing Secrets Securely

For sensitive values like database passwords and API keys, use Pulumi's secret management:

# Set a secret value (encrypted at rest)
pulumi config set dbPassword --secret "your-secure-password"
import * as pulumi from "@pulumi/pulumi";

const config = new pulumi.Config();
const dbPassword = config.requireSecret("dbPassword");

// The password is encrypted in state and marked as secret
const instance = new aws.rds.Instance("db", {
 password: dbPassword,
});

Using Stack References

When infrastructure is split across projects, use stack references to share outputs:

import * as pulumi from "@pulumi/pulumi";

const networkStack = new pulumi.StackReference("my-org/network/vpc");

// Reference outputs from another stack
const vpcId = networkStack.getOutput("vpcId");
const subnetIds = networkStack.getOutput("subnetIds");

Testing Infrastructure as Code

Testing infrastructure code validates that your definitions produce the expected resources. Pulumi supports multiple testing approaches, as demonstrated in this practical implementation guide.

Property Testing

Property testing verifies that resources have expected properties after creation:

import * as pulumitest from "@pulumi/pulumi/testing";

const test = pulumitest.createTest("S3 bucket configuration test", async () => {
 // Provision resources
 const bucket = new aws.s3.Bucket("test-bucket", {
 versioning: { enabled: true },
 });

 // Verify configuration
 const bucketInfo = await bucket.get();
 assert(bucketInfo.versioning.enabled === true);
});

Integration Testing

Integration testing validates that resources work correctly in your cloud environment:

import * as aws from "@pulumi/aws";

async function testBucketAccess() {
 const bucket = new aws.s3.Bucket("test-bucket", {
 acl: "private",
 });

 // Wait for resource to be created
 await bucket.id.apply(async (bucketName) => {
 const s3 = new aws.sdk.S3();
 await s3.putObject({
 Bucket: bucketName,
 Key: "test.txt",
 Body: "Hello World",
 }).promise();
 });
}

Linting and Static Analysis

Use TypeScript's compiler and additional tools for code quality:

# Type checking
npx tsc --noEmit

# Linting with eslint
npx eslint *.ts --ext .ts

Comparing Pulumi With TypeScript to Alternatives

Pulumi vs Terraform

While Terraform uses HCL (HashiCorp Configuration Language), Pulumi uses general-purpose languages:

AspectPulumi + TypeScriptTerraform
LanguageTypeScript/JavaScriptHCL
Type SafetyCompile-time checkingLimited (no true types)
IDE SupportFull autocomplete, refactoringBasic text completion
TestingUnit, integration, property testsLimited (external tools)
VariablesStrong typing with TypeScriptHCL variables
ConditionalsFull TypeScript expressionsLimited HCL expressions

Pulumi vs CloudFormation/Terraform

Pulumi's multi-cloud support means you can manage AWS, Azure, GCP, and Kubernetes resources in a single project. For teams implementing comprehensive web development solutions, this unified approach simplifies infrastructure management across environments.

// Mix resources from multiple providers
import * as aws from "@pulumi/aws";
import * as azure from "@pulumi/azure";

// AWS S3 bucket
const bucket = new aws.s3.Bucket("storage", {
 bucket: "my-bucket",
});

// Azure Storage account
const storageAccount = new azure.storage.Account("storage", {
 name: "mystorageaccount",
 resourceGroupName: "my-resource-group",
 accountTier: "Standard",
});

Performance Considerations

Resource Creation Speed

Pulumi provisions resources in parallel where dependencies allow. For large infrastructures:

  • Use dependsOn sparingly - only when implicit dependencies aren't captured
  • Group related resources in the same deployment for better parallelization
  • Use ignoreChanges for frequently changing optional properties

State Management

Pulumi's state file tracks all managed resources. For team environments:

  • Use Pulumi Cloud or a self-hosted backend for state storage
  • Enable state encryption for sensitive data
  • Use resource namespacing to organize large deployments

Preview and Planning

Always review pulumi preview before pulumi up:

# See what would be created/modified/destroyed
pulumi preview

# With detailed diff
pulumi preview --diff

# Show full JSON plan
pulumi preview --json
Getting Started Checklist

Your roadmap to beginning with Pulumi and TypeScript

Install Prerequisites

Node.js, Pulumi CLI, and cloud provider credentials

Create a Project

Run `pulumi new typescript` to bootstrap your project

Configure Providers

Set up AWS, Azure, or GCP access with proper IAM permissions

Define Resources

Write TypeScript code for your cloud infrastructure

Conclusion

Pulumi with TypeScript brings modern software development practices to infrastructure management. The combination of strong typing, familiar syntax, and powerful IDE support makes it an excellent choice for teams already invested in the TypeScript ecosystem.

By leveraging TypeScript's type system, you catch configuration errors early. By using Pulumi's stack management, you create isolated environments for development, testing, and production. By organizing code into modules, you maintain clarity even as infrastructure grows complex.

The approach represents a shift from configuration files to programmatic infrastructure definition - one that aligns infrastructure work with the skills and workflows modern development teams already use. Our web development services team specializes in implementing these modern infrastructure approaches to deliver scalable, maintainable solutions for complex projects.

Ready to Modernize Your Infrastructure Workflow?

Our team of TypeScript and cloud infrastructure experts can help you adopt Pulumi and build scalable, maintainable infrastructure code.

Frequently Asked Questions

What is Pulumi and how does it differ from Terraform?

Pulumi is an infrastructure as code tool that uses general-purpose programming languages like TypeScript instead of domain-specific languages like HCL. This provides better type safety, IDE support, and allows you to use familiar testing and code organization patterns.

Do I need to know TypeScript to use Pulumi?

While knowing TypeScript helps, Pulumi's approach makes infrastructure accessible to developers familiar with any programming language. The learning curve is minimal if you have basic TypeScript or JavaScript experience.

Can Pulumi manage multiple cloud providers?

Yes, Pulumi supports AWS, Azure, Google Cloud, Kubernetes, and many other providers in a single project. You can mix resources from different providers in the same codebase without needing separate tools or configurations.

How does Pulumi handle secrets and sensitive data?

Pulumi has built-in secret management that encrypts sensitive values in state and configuration. Use `pulumi config set --secret` to store secrets securely.

What testing approaches work with Pulumi?

Pulumi supports unit testing, property testing, and integration testing. You can use TypeScript testing frameworks like Jest along with Pulumi's testing utilities to validate your infrastructure code.