AI Tools Compared

Writing Pulumi infrastructure-as-code (IaC) requires understanding both cloud provider APIs and programmatic resource composition. AI assistants excel at this because Pulumi’s code-first approach maps well to how LLMs reason about code generation. Claude, GitHub Copilot, and Cursor each have distinct strengths when generating TypeScript and Python Pulumi programs. The key difference lies in context window size, code understanding depth, and real-time completion accuracy.

Why Pulumi IaC Is Different From Other Code

Traditional Terraform uses HCL, a declarative language specifically designed for infrastructure. Pulumi uses general-purpose languages (Python, TypeScript, Go, C#) to define infrastructure through SDK calls. This means you’re writing code that calls Pulumi provider APIs, manages state through object references, and often needs to handle cross-stack dependencies.

AI assistants trained on large codebases understand both language semantics and Pulumi SDK patterns. However, they differ significantly in:

Tool Comparison: Claude vs Copilot vs Cursor

Claude (Opus or Sonnet)

Claude excels at Pulumi IaC generation through its extended context window (200K tokens) and deep code reasoning. You can paste your entire existing stack, architecture diagrams, and AWS/GCP documentation simultaneously.

Strengths:

Weaknesses:

Real pricing: Claude API: $3/1M input tokens, $15/1M output tokens (Opus). For a 50,000-token infrastructure context: ~$0.15 input cost.

Example prompt pattern:

I have a TypeScript Pulumi stack that creates:
1. VPC with 3 subnets across 2 AZs
2. RDS Aurora cluster
3. ECS cluster with service discovery

[Paste full existing Pulumi code here]

Now I need to add a Lambda function that reads from the Aurora cluster and publishes metrics to CloudWatch. The Lambda needs:
- IAM role with RDS read access
- VPC subnet assignment
- CloudWatch Logs group

Generate the Pulumi TypeScript code following my existing patterns.

Claude will generate complete, idiomatic TypeScript that matches your existing code style and properly references the Aurora endpoint through stack exports.

GitHub Copilot

Copilot operates directly in your IDE and learns from your codebase. It’s optimized for velocity—quick completions and line-by-line suggestions. Copilot uses GPT-4 as its underlying model but with codebase-specific context.

Strengths:

Weaknesses:

Real pricing: $10/month for individuals, $21/month for business accounts. Enterprise: custom pricing.

Practical example: You’re writing a new ECS service definition. Copilot watches your existing Pulumi patterns and suggests:

const taskRole = new aws.iam.Role("app-task-role", {
  assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal(
    aws.iam.Principals.EcsTasksPrincipal
  ),
});

Copilot learns from your codebase that you use specific role naming conventions and immediately suggests completions aligned with your style.

Cursor IDE

Cursor is an AI-native IDE built on VSCode. It provides AI assistance directly in your editor with both fast completions and longer-form generation through Ctrl+K. Cursor supports multiple backend models (Claude, GPT-4, Sonnet).

Strengths:

Weaknesses:

Real pricing: $20/month for Cursor Pro with unlimited Claude usage.

Practical workflow: You open your infrastructure/vpc.ts file. Cursor sees the entire project structure. You press Cmd+K and write:

Generate a cross-stack reference module that exports the VPC ID, subnet IDs,
and security group ID from the VPC stack, then show how to import and use it
in the application stack.

Cursor generates both files, understanding the project structure automatically.

Comparison Table: Capabilities Matrix

Feature Claude Copilot Cursor
Context Window 200K tokens 8K tokens 50K+ (with Claude backend)
Pulumi SDK Accuracy High (with explicit version mention) Medium (often outdated) High (with Claude backend)
Real-time Completion No (requires tool) Yes Yes
Cross-file Understanding Excellent Poor Excellent
Cost per 1000 tokens $0.003-0.015 $0.83/month flat $20/month flat
IDE Integration Via extension Native Native
Multi-service coordination Excellent Good Excellent
Stack reference generation Excellent Fair Excellent
Learning from codebase No Yes Yes

Real-World Examples: TypeScript Pulumi Generation

Example 1: VPC + RDS Stack with Network Access

Prompt to Claude:

I'm using Pulumi TypeScript to create a secure RDS PostgreSQL cluster.

Requirements:
- VPC with private subnets in 2 AZs (10.0.0.0/16)
- RDS cluster spanning both AZs
- Security group allowing PostgreSQL only from app subnets
- Output the cluster endpoint and port for cross-stack reference
- Use encryption at rest with AWS managed keys
- Enable automated backups with 7-day retention

Claude’s output (simplified):

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

const vpc = new aws.ec2.Vpc("app-vpc", {
  cidrBlock: "10.0.0.0/16",
  enableDnsHostnames: true,
  enableDnsSupport: true,
});

const privateSubnets = [];
for (let i = 0; i < 2; i++) {
  const subnet = new aws.ec2.Subnet(`private-subnet-${i}`, {
    vpcId: vpc.id,
    cidrBlock: `10.0.${10 + i}.0/24`,
    availabilityZone: aws.getAvailabilityZones({
      state: "available",
    }).then(azs => azs.names[i]),
  });
  privateSubnets.push(subnet);
}

const dbSecurityGroup = new aws.ec2.SecurityGroup("db-sg", {
  vpcId: vpc.id,
  ingress: [{
    protocol: "tcp",
    fromPort: 5432,
    toPort: 5432,
    securityGroups: [appSecurityGroupId], // from app stack
  }],
});

const dbSubnetGroup = new aws.rds.SubnetGroup("db-subnet-group", {
  subnetIds: privateSubnets.map(s => s.id),
});

const rdsCluster = new aws.rds.Cluster("postgres-cluster", {
  engine: "aurora-postgresql",
  engineVersion: "15.2",
  databaseName: "appdb",
  masterUsername: "postgres",
  masterPassword: config.require("dbPassword"),
  dbSubnetGroupName: dbSubnetGroup.name,
  vpcSecurityGroupIds: [dbSecurityGroup.id],
  storageEncrypted: true,
  backupRetentionPeriod: 7,
  skipFinalSnapshot: false,
  finalSnapshotIdentifier: "final-snapshot-${Date.now()}",
  preferredBackupWindow: "03:00-04:00",
});

export const dbEndpoint = rdsCluster.endpoint;
export const dbPort = rdsCluster.port;

This approach:

Example 2: ECS Service with Auto-scaling

Prompt to Claude:

Create a Pulumi TypeScript ECS service with:
- Service runs in existing VPC (pass in subnet/SG IDs from stack ref)
- Container image from ECR with tag-based updates
- Auto-scaling from 2-10 tasks based on CPU 70% threshold
- CloudWatch logs
- ALB target group attachment
- Environment variables from Secrets Manager

Assume previous stack exports dbEndpoint and dbPassword.

Result pattern (key sections):

const dbStackRef = new pulumi.StackReference("prod/db");
const dbEndpoint = dbStackRef.getOutput("dbEndpoint");
const dbPassword = dbStackRef.getOutput("dbPassword");

const taskExecutionRole = new aws.iam.Role("ecs-task-exec-role", {
  assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({
    Service: "ecs-tasks.amazonaws.com",
  }),
});

const taskRole = new aws.iam.Role("ecs-task-role", {
  assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({
    Service: "ecs-tasks.amazonaws.com",
  }),
});

// Attach policy to read from Secrets Manager
new aws.iam.RolePolicyAttachment("task-secrets", {
  role: taskRole,
  policyArn: "arn:aws:iam::aws:policy/SecretsManagerReadWrite",
});

const taskDefinition = new aws.ecs.TaskDefinition("app-task", {
  family: "app",
  networkMode: "awsvpc",
  requiresCompatibilities: ["FARGATE"],
  cpu: "256",
  memory: "512",
  executionRoleArn: taskExecutionRole.arn,
  taskRoleArn: taskRole.arn,
  containerDefinitions: pulumi.output([{
    name: "app",
    image: ecr.image.imageUri,
    portMappings: [{
      containerPort: 8080,
      hostPort: 8080,
      protocol: "tcp",
    }],
    environment: [
      { name: "DB_HOST", value: dbEndpoint },
      { name: "LOG_LEVEL", value: "info" },
    ],
    logConfiguration: {
      logDriver: "awslogs",
      options: {
        "awslogs-group": cloudwatchLogGroup.name,
        "awslogs-region": aws.getRegion().then(r => r.name),
        "awslogs-stream-prefix": "ecs",
      },
    },
  }]).apply(JSON.stringify),
});

const service = new aws.ecs.Service("app-service", {
  cluster: clusterArn,
  taskDefinition: taskDefinition.arn,
  desiredCount: 2,
  launchType: "FARGATE",
  networkConfiguration: {
    subnets: subnetIds,
    securityGroups: [sgId],
    assignPublicIp: false,
  },
  loadBalancers: [{
    targetGroupArn: targetGroupArn,
    containerName: "app",
    containerPort: 8080,
  }],
});

const autoscaling = new aws.appautoscaling.Target("ecs-autoscaling", {
  maxCapacity: 10,
  minCapacity: 2,
  resourceId: pulumi.interpolate`service/${clusterName}/${service.name}`,
  scalableDimension: "ecs:service:DesiredCount",
  serviceNamespace: "ecs",
});

new aws.appautoscaling.Policy("cpu-scaling", {
  policyType: "TargetTrackingScaling",
  resourceId: autoscaling.resourceId,
  scalableDimension: autoscaling.scalableDimension,
  serviceNamespace: autoscaling.serviceNamespace,
  targetTrackingScalingPolicyConfiguration: {
    predefinedMetricSpecification: {
      predefinedMetricType: "ECSServiceAverageCPUUtilization",
    },
    targetValue: 70.0,
  },
});

Claude generates complete, production-ready patterns including proper IAM roles, cross-stack references, and auto-scaling thresholds.

Practical Workflow: Which Tool for Which Task

Use Claude When:

Use Copilot When:

Use Cursor When:

CLI Commands for Pulumi Workflow

Deploy and preview your generated code:

# Initialize a new Pulumi project
pulumi new aws-typescript

# Preview changes before deployment
pulumi preview --stack prod

# Deploy infrastructure
pulumi up --stack prod

# Refresh stack state (useful after manual changes)
pulumi refresh --stack prod

# Destroy infrastructure
pulumi destroy --stack prod

# List stack references available to other stacks
pulumi stack select prod
pulumi stack export

Decision Framework: Accuracy vs Velocity

Ask yourself these questions:

  1. How large is the infrastructure change? (1-2 resources vs entire new stack)
    • Small: Use Copilot for velocity
    • Large: Use Claude for accuracy
  2. Do you need to reference existing infrastructure?
    • Yes, across many stacks: Claude’s context window wins
    • No, isolated resources: Copilot/Cursor sufficient
  3. Is your team already on a specific IDE?
    • Already on VSCode: Cursor eliminates friction
    • Different editors: Claude API is universal
  4. What’s your error tolerance?
    • Low (production): Claude with explicit version pinning
    • Medium (staging): Copilot is acceptable
    • High (dev): Any tool with quick iteration
  5. How often do you write IaC?
    • Daily: Cursor ($20/month) pays for itself
    • Weekly: Claude ($3-5/week API cost) more economical
    • Monthly: Copilot ($10/month) simplest

Common Mistakes AI Tools Make

All three tools occasionally generate:

Testing Generated Infrastructure Code

Before deploying generated Pulumi code:

# Lint TypeScript
npx tsc --noEmit

# Validate stack can be serialized
pulumi preview --verbose

# Check for unused variables
npm run lint

# Test stack references in isolation
pulumi stack ls
pulumi stack select staging
pulumi stack export > export.json

Cost Comparison: Per-Project Basis

For a typical infrastructure project (5 stacks, ~1000 lines total):

Tool Cost for Project Cost per Year
Claude (API) $5-15 $60-180
Copilot $10/month $120
Cursor $20/month $240
Multiple tools combined $10-20 $120-240

Most teams find combined approach optimal: Cursor for daily editing, Claude for complex designs, Copilot as fallback.

Built by theluckystrike — More at zovo.one