Pulumi
DevOps Tool
Pulumi
Overview
Pulumi is a modern Infrastructure as Code (IaC) platform that uses programming languages like TypeScript, Python, Go, C#, and Java to define infrastructure. Instead of traditional DSL (Domain Specific Language), it leverages familiar programming languages and supports over 150 cloud providers.
Details
Founded in 2017, Pulumi was developed with the philosophy of "true code-based infrastructure management." Rather than using DSL like Terraform, it allows developers to define infrastructure using general-purpose programming languages (TypeScript, Python, Go, C#, Java) that they already know.
This approach significantly improves developer productivity by leveraging existing development tools (IDEs, debuggers, package managers, testing frameworks) directly. Pulumi provides over 14,000 code snippets and rich implementation examples, continuing to grow rapidly with its developer-friendly approach.
A unique feature of Pulumi is the ability to directly utilize programming language features (conditionals, loops, functions, classes, package management) in infrastructure definitions. Additionally, the Automation API enables embedding infrastructure management into applications.
Pros and Cons
Pros
- Programming language usage: Leverage existing development skills and tools directly
- Rich providers: Support for over 150 cloud providers
- Development tool integration: Utilize IDEs, debuggers, and testing frameworks
- Automation API: Automate infrastructure management programmatically
- Strong type system: Compile-time error detection and IntelliSense
Cons
- Learning curve: Requires programming language knowledge (high barrier for non-programmers)
- Complexity: Potential to write overly complex code
- Tool dependency: Dependent on each language's ecosystem
- Performance: Execution time may be longer in large-scale environments
References
Official Resources
- Pulumi Official Site - Official website
- Pulumi Documentation - Official documentation
- Pulumi Registry - Provider and package search
- Pulumi GitHub - Source code
Learning Resources
- Get Started with Pulumi - Official tutorials
- Pulumi Examples - Implementation examples
- Pulumi Workshops - Hands-on workshops
Usage Examples
Basic Project Creation (TypeScript)
# Create new project
mkdir my-pulumi-app && cd my-pulumi-app
# Initialize with TypeScript template
pulumi new typescript
# AWS project template
pulumi new aws-typescript
# Azure project template
pulumi new azure-typescript
# GCP project template
pulumi new gcp-typescript
# Kubernetes project template
pulumi new kubernetes-typescript
AWS S3 Bucket Creation (TypeScript)
// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Create S3 bucket
const bucket = new aws.s3.Bucket("my-bucket", {
// Bucket name is auto-generated
bucket: undefined,
// Public access block
publicAccessBlock: new aws.s3.BucketPublicAccessBlock("bucket-pab", {
bucket: bucket.id,
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true,
}),
// Server-side encryption
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "AES256",
},
},
},
// Enable versioning
versioning: {
enabled: true,
},
});
// Export bucket name
export const bucketName = bucket.id;
export const bucketUrl = pulumi.interpolate`https://${bucket.bucket}.s3.amazonaws.com`;
Conditionals and Loops (TypeScript)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment"); // dev, staging, prod
// Environment-based instance type selection
const instanceType = environment === "prod" ? "t3.large" : "t3.micro";
// Multi-AZ configuration for production only
const subnetIds = environment === "prod"
? ["subnet-12345", "subnet-67890", "subnet-abcdef"]
: ["subnet-12345"];
// Create multiple web servers with loop
const webServers: aws.ec2.Instance[] = [];
for (let i = 0; i < subnetIds.length; i++) {
const server = new aws.ec2.Instance(`web-server-${i}`, {
ami: "ami-0c94855ba95b798c7", // Amazon Linux 2
instanceType: instanceType,
subnetId: subnetIds[i],
tags: {
Name: `WebServer-${environment}-${i + 1}`,
Environment: environment,
},
});
webServers.push(server);
}
// Loop using object array
const environments = [
{ name: "dev", count: 1 },
{ name: "staging", count: 2 },
{ name: "prod", count: 3 },
];
environments.forEach(env => {
for (let i = 0; i < env.count; i++) {
new aws.ec2.Instance(`${env.name}-instance-${i}`, {
ami: "ami-0c94855ba95b798c7",
instanceType: env.name === "prod" ? "t3.large" : "t3.micro",
tags: {
Name: `${env.name}-instance-${i}`,
Environment: env.name,
},
});
}
});
Component Resource Creation (TypeScript)
// webserver.ts - Reusable component
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export interface WebServerArgs {
vpcId: pulumi.Input<string>;
subnetId: pulumi.Input<string>;
instanceType?: string;
keyName?: 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:WebServer", name, {}, opts);
// Create security group
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"],
},
{
protocol: "tcp",
fromPort: 22,
toPort: 22,
cidrBlocks: ["0.0.0.0/0"],
},
],
egress: [{
protocol: "-1",
fromPort: 0,
toPort: 0,
cidrBlocks: ["0.0.0.0/0"],
}],
}, { parent: this });
// Create EC2 instance
this.instance = new aws.ec2.Instance(`${name}-instance`, {
ami: "ami-0c94855ba95b798c7", // Amazon Linux 2
instanceType: args.instanceType || "t3.micro",
subnetId: args.subnetId,
keyName: args.keyName,
vpcSecurityGroupIds: [this.securityGroup.id],
userData: `#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from ${name}!</h1>" > /var/www/html/index.html
`,
tags: {
Name: name,
},
}, { parent: this });
this.publicIp = this.instance.publicIp;
// Register component outputs
this.registerOutputs({
instance: this.instance,
securityGroup: this.securityGroup,
publicIp: this.publicIp,
});
}
}
// Usage example (index.ts)
import { WebServer } from "./webserver";
const vpc = new aws.ec2.Vpc("main-vpc", {
cidrBlock: "10.0.0.0/16",
});
const subnet = new aws.ec2.Subnet("public-subnet", {
vpcId: vpc.id,
cidrBlock: "10.0.1.0/24",
mapPublicIpOnLaunch: true,
});
const webServer = new WebServer("my-web-server", {
vpcId: vpc.id,
subnetId: subnet.id,
instanceType: "t3.small",
});
export const serverUrl = pulumi.interpolate`http://${webServer.publicIp}`;
Python Implementation Example
# __main__.py
import pulumi
import pulumi_aws as aws
# Get configuration values
config = pulumi.Config()
environment = config.require("environment")
# Create VPC
vpc = aws.ec2.Vpc("main-vpc",
cidr_block="10.0.0.0/16",
enable_dns_hostnames=True,
enable_dns_support=True,
tags={
"Name": f"{environment}-vpc",
"Environment": environment,
}
)
# Internet Gateway
igw = aws.ec2.InternetGateway("main-igw",
vpc_id=vpc.id,
tags={
"Name": f"{environment}-igw",
}
)
# Public subnet
public_subnet = aws.ec2.Subnet("public-subnet",
vpc_id=vpc.id,
cidr_block="10.0.1.0/24",
map_public_ip_on_launch=True,
tags={
"Name": f"{environment}-public-subnet",
}
)
# Route table
route_table = aws.ec2.RouteTable("public-route-table",
vpc_id=vpc.id,
routes=[{
"cidr_block": "0.0.0.0/0",
"gateway_id": igw.id,
}],
tags={
"Name": f"{environment}-public-rt",
}
)
# Route table association
aws.ec2.RouteTableAssociation("public-route-table-association",
subnet_id=public_subnet.id,
route_table_id=route_table.id
)
# Security group
security_group = aws.ec2.SecurityGroup("web-sg",
vpc_id=vpc.id,
description="Security group for web server",
ingress=[
{
"protocol": "tcp",
"from_port": 80,
"to_port": 80,
"cidr_blocks": ["0.0.0.0/0"],
},
{
"protocol": "tcp",
"from_port": 443,
"to_port": 443,
"cidr_blocks": ["0.0.0.0/0"],
},
],
egress=[{
"protocol": "-1",
"from_port": 0,
"to_port": 0,
"cidr_blocks": ["0.0.0.0/0"],
}],
tags={
"Name": f"{environment}-web-sg",
}
)
# EC2 instance
instance = aws.ec2.Instance("web-server",
ami="ami-0c94855ba95b798c7", # Amazon Linux 2
instance_type="t3.micro" if environment != "prod" else "t3.small",
subnet_id=public_subnet.id,
vpc_security_group_ids=[security_group.id],
user_data="""#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Pulumi Python!</h1>" > /var/www/html/index.html
""",
tags={
"Name": f"{environment}-web-server",
"Environment": environment,
}
)
# Outputs
pulumi.export("vpc_id", vpc.id)
pulumi.export("instance_ip", instance.public_ip)
pulumi.export("website_url", instance.public_ip.apply(lambda ip: f"http://{ip}"))
Automation API (Programmatic Execution)
// automation.ts - Execute Pulumi programmatically
import * as pulumi from "@pulumi/pulumi";
import { LocalWorkspace } from "@pulumi/pulumi/automation";
import * as aws from "@pulumi/aws";
// Infrastructure defined as a program
const pulumiProgram = async () => {
const bucket = new aws.s3.Bucket("auto-bucket", {
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "AES256",
},
},
},
});
return {
bucketName: bucket.id,
};
};
async function main() {
const stackName = "dev";
const projectName = "automation-example";
// Create Pulumi workspace
const stack = await LocalWorkspace.createOrSelectStack({
stackName,
projectName,
program: pulumiProgram,
});
console.log("Refreshing stack...");
await stack.refresh({ onOutput: console.info });
console.log("Setting up configuration...");
await stack.setConfig("aws:region", { value: "us-west-2" });
console.log("Updating stack...");
const upRes = await stack.up({ onOutput: console.info });
console.log(`Update summary: ${upRes.summary.resourceChanges}`);
console.log(`Bucket name: ${upRes.outputs.bucketName.value}`);
}
main().catch(console.error);
Kubernetes Resource Management
// k8s.ts
import * as k8s from "@pulumi/kubernetes";
import * as pulumi from "@pulumi/pulumi";
// Create Namespace
const namespace = new k8s.core.v1.Namespace("app-namespace", {
metadata: {
name: "my-app",
},
});
// ConfigMap
const configMap = new k8s.core.v1.ConfigMap("app-config", {
metadata: {
namespace: namespace.metadata.name,
name: "app-config",
},
data: {
"config.json": JSON.stringify({
database: {
host: "localhost",
port: 5432,
},
features: {
logging: true,
monitoring: true,
},
}),
},
});
// Secret
const secret = new k8s.core.v1.Secret("app-secret", {
metadata: {
namespace: namespace.metadata.name,
name: "app-secret",
},
type: "Opaque",
stringData: {
"db-password": "super-secret-password",
"api-key": "abcd1234-5678-90ef",
},
});
// Deployment
const deployment = new k8s.apps.v1.Deployment("app-deployment", {
metadata: {
namespace: namespace.metadata.name,
name: "my-app",
},
spec: {
replicas: 3,
selector: {
matchLabels: {
app: "my-app",
},
},
template: {
metadata: {
labels: {
app: "my-app",
},
},
spec: {
containers: [{
name: "app",
image: "nginx:1.21",
ports: [{
containerPort: 80,
}],
env: [
{
name: "DB_PASSWORD",
valueFrom: {
secretKeyRef: {
name: secret.metadata.name,
key: "db-password",
},
},
},
],
volumeMounts: [{
name: "config-volume",
mountPath: "/etc/config",
}],
}],
volumes: [{
name: "config-volume",
configMap: {
name: configMap.metadata.name,
},
}],
},
},
},
});
// Service
const service = new k8s.core.v1.Service("app-service", {
metadata: {
namespace: namespace.metadata.name,
name: "my-app-service",
},
spec: {
selector: {
app: "my-app",
},
ports: [{
port: 80,
targetPort: 80,
}],
type: "LoadBalancer",
},
});
export const serviceEndpoint = service.status.loadBalancer.ingress[0].ip;
Basic Pulumi Commands
# Project initialization
pulumi new typescript
pulumi new aws-typescript
pulumi new kubernetes-typescript
# Stack management
pulumi stack init dev
pulumi stack select dev
pulumi stack ls
# Configuration management
pulumi config set aws:region us-west-2
pulumi config set myapp:environment dev
pulumi config set --secret myapp:apiKey abc123
# Deployment
pulumi preview # Preview (equivalent to terraform plan)
pulumi up # Execute deployment
pulumi up --yes # Skip confirmation
# State checking
pulumi stack output
pulumi stack output bucketName
pulumi logs
# Resource management
pulumi state delete <resource-urn>
pulumi import <resource-type> <name> <id>
# Destroy
pulumi destroy
pulumi destroy --yes
# Others
pulumi console # Open console in browser
pulumi convert --from terraform --language typescript # Convert from Terraform
Development Workflow
1. Project Setup
# Create new project
mkdir my-infrastructure && cd my-infrastructure
pulumi new aws-typescript
# Install dependencies
npm install
# Configuration
pulumi config set aws:region us-east-1
2. Development Cycle
# 1. Create/edit code
code index.ts
# 2. Preview (check changes)
pulumi preview
# 3. Deploy
pulumi up
# 4. Verify operation
pulumi stack output
curl $(pulumi stack output endpointUrl)
# 5. Check logs
pulumi logs
3. CI/CD Integration
# .github/workflows/pulumi.yml
name: Pulumi Infrastructure
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
pulumi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm install
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Pulumi Preview
uses: pulumi/actions@v3
with:
command: preview
stack-name: dev
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
- name: Pulumi Deploy
if: github.ref == 'refs/heads/main'
uses: pulumi/actions@v3
with:
command: up
stack-name: production
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}