AWS Provider Setup
Why AWS with Pulumi?
The Problem: AWS has 200+ services and the console makes it hard to reproduce infrastructure reliably across environments.
The Solution: Pulumi lets you define AWS infrastructure using real programming languages with type safety, IDE support, and full abstraction capabilities.
Real Impact: Teams using Pulumi with AWS reduce infrastructure provisioning time by up to 80% and eliminate configuration drift entirely.
Real-World Analogy
Think of Pulumi with AWS as an architect's blueprint system:
- AWS Provider = The construction company that builds from blueprints
- VPC = The land and fencing around your building complex
- Subnets = Different zones within your property (public lobby, private offices)
- EC2 Instances = Individual buildings on your property
- S3 Buckets = Storage warehouses for your documents
Key AWS Concepts in Pulumi
Provider Configuration
Configure AWS credentials, region, and profile settings to authenticate Pulumi with your AWS account.
Resource Naming
Pulumi auto-generates unique names for AWS resources, preventing naming conflicts across stacks.
Cross-Region Deploy
Use multiple provider instances to deploy resources across different AWS regions from one program.
Tagging Strategy
Apply consistent tags to all resources using transformations for cost tracking and organization.
Installing the AWS Provider
# Create a new Pulumi project for AWS
pulumi new aws-typescript
# Install the AWS provider package
npm install @pulumi/aws
# Configure the AWS region
pulumi config set aws:region us-east-1
# Optionally set an AWS profile
pulumi config set aws:profile my-profile
Provider Configuration in Code
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Default provider uses pulumi config values
const defaultVpc = new aws.ec2.Vpc("default-vpc", {
cidrBlock: "10.0.0.0/16",
});
// Explicit provider for a different region
const westProvider = new aws.Provider("west", {
region: "us-west-2",
});
const westVpc = new aws.ec2.Vpc("west-vpc", {
cidrBlock: "10.1.0.0/16",
}, { provider: westProvider });
VPC & Networking
Creating a VPC with Subnets
import * as aws from "@pulumi/aws";
// Create a VPC
const vpc = new aws.ec2.Vpc("main-vpc", {
cidrBlock: "10.0.0.0/16",
enableDnsHostnames: true,
enableDnsSupport: true,
tags: { Name: "main-vpc", Environment: "production" },
});
// Create public subnets
const publicSubnet = new aws.ec2.Subnet("public-subnet", {
vpcId: vpc.id,
cidrBlock: "10.0.1.0/24",
availabilityZone: "us-east-1a",
mapPublicIpOnLaunch: true,
tags: { Name: "public-subnet" },
});
// Create private subnets
const privateSubnet = new aws.ec2.Subnet("private-subnet", {
vpcId: vpc.id,
cidrBlock: "10.0.2.0/24",
availabilityZone: "us-east-1a",
tags: { Name: "private-subnet" },
});
// Internet Gateway for public access
const igw = new aws.ec2.InternetGateway("igw", {
vpcId: vpc.id,
tags: { Name: "main-igw" },
});
// Route table for public subnet
const publicRouteTable = new aws.ec2.RouteTable("public-rt", {
vpcId: vpc.id,
routes: [{
cidrBlock: "0.0.0.0/0",
gatewayId: igw.id,
}],
});
new aws.ec2.RouteTableAssociation("public-rta", {
subnetId: publicSubnet.id,
routeTableId: publicRouteTable.id,
});
export const vpcId = vpc.id;
export const publicSubnetId = publicSubnet.id;
Compute (EC2 & Lambda)
Launching an EC2 Instance
// Security group for web traffic
const webSg = new aws.ec2.SecurityGroup("web-sg", {
vpcId: vpc.id,
description: "Allow HTTP and SSH",
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: ["10.0.0.0/16"] },
],
egress: [
{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] },
],
});
// Look up the latest Amazon Linux 2 AMI
const ami = aws.ec2.getAmi({
mostRecent: true,
owners: ["amazon"],
filters: [{ name: "name", values: ["amzn2-ami-hvm-*-x86_64-gp2"] }],
});
// Launch an EC2 instance
const server = new aws.ec2.Instance("web-server", {
instanceType: "t3.micro",
ami: ami.then(a => a.id),
subnetId: publicSubnet.id,
vpcSecurityGroupIds: [webSg.id],
userData: `#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "Hello from Pulumi!" > /var/www/html/index.html`,
tags: { Name: "web-server" },
});
export const publicIp = server.publicIp;
Creating a Lambda Function
// IAM role for Lambda
const lambdaRole = new aws.iam.Role("lambda-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Principal: { Service: "lambda.amazonaws.com" },
Effect: "Allow",
}],
}),
});
new aws.iam.RolePolicyAttachment("lambda-basic", {
role: lambdaRole.name,
policyArn: aws.iam.ManagedPolicies.AWSLambdaBasicExecutionRole,
});
// Create the Lambda function
const fn = new aws.lambda.Function("api-handler", {
runtime: "nodejs18.x",
handler: "index.handler",
role: lambdaRole.arn,
code: new pulumi.asset.AssetArchive({
"index.js": new pulumi.asset.StringAsset(
`exports.handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello from Pulumi Lambda!" }),
};
};`
),
}),
environment: {
variables: { STAGE: pulumi.getStack() },
},
});
export const functionName = fn.name;
Storage (S3 & DynamoDB)
S3 Bucket with Configuration
// S3 bucket with versioning and encryption
const bucket = new aws.s3.BucketV2("data-bucket", {
tags: { Environment: pulumi.getStack() },
});
new aws.s3.BucketVersioningV2("data-versioning", {
bucket: bucket.id,
versioningConfiguration: { status: "Enabled" },
});
new aws.s3.BucketServerSideEncryptionConfigurationV2("data-encryption", {
bucket: bucket.id,
rules: [{
applyServerSideEncryptionByDefault: {
sseAlgorithm: "aws:kms",
},
}],
});
// Block all public access
new aws.s3.BucketPublicAccessBlock("data-public-block", {
bucket: bucket.id,
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true,
});
// DynamoDB table
const table = new aws.dynamodb.Table("app-table", {
attributes: [
{ name: "pk", type: "S" },
{ name: "sk", type: "S" },
],
hashKey: "pk",
rangeKey: "sk",
billingMode: "PAY_PER_REQUEST",
tags: { Environment: pulumi.getStack() },
});
export const bucketName = bucket.bucket;
export const tableName = table.name;
IAM & Security
IAM Roles and Policies
// Create an IAM role for an application
const appRole = new aws.iam.Role("app-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Principal: { Service: "ec2.amazonaws.com" },
Effect: "Allow",
}],
}),
});
// Custom inline policy for S3 access
const s3Policy = new aws.iam.RolePolicy("s3-access", {
role: appRole.id,
policy: bucket.arn.apply(arn => JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: ["s3:GetObject", "s3:PutObject"],
Resource: `${arn}/*`,
}],
})),
});
// Instance profile to attach role to EC2
const instanceProfile = new aws.iam.InstanceProfile("app-profile", {
role: appRole.name,
});
Quick Reference
AWS Resource Cheat Sheet
| Resource | Pulumi Class | Key Properties |
|---|---|---|
| VPC | aws.ec2.Vpc |
cidrBlock, enableDnsHostnames, tags |
| Subnet | aws.ec2.Subnet |
vpcId, cidrBlock, availabilityZone |
| EC2 Instance | aws.ec2.Instance |
instanceType, ami, subnetId, userData |
| Lambda | aws.lambda.Function |
runtime, handler, role, code |
| S3 Bucket | aws.s3.BucketV2 |
tags, forceDestroy |
| DynamoDB | aws.dynamodb.Table |
attributes, hashKey, billingMode |
| IAM Role | aws.iam.Role |
assumeRolePolicy, tags |
Essential CLI Commands
Common Workflows
pulumi up- Preview and deploy changes to AWSpulumi preview- See what changes will be made without deployingpulumi stack output- View exported values like IPs and ARNspulumi destroy- Tear down all AWS resources in the stackpulumi refresh- Sync state with actual AWS resources