A Pulumi package for creating and managing Amazon Web Services (AWS) cloud resources with infrastructure-as-code.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
This guide explains the fundamental concepts you need to understand when working with Pulumi and AWS.
Resources are the fundamental building blocks of Pulumi infrastructure. Each resource represents a cloud infrastructure object managed by Pulumi.
import * as aws from "@pulumi/aws";
// Resource declaration
const bucket = new aws.s3.Bucket("my-bucket", {
bucketName: "my-unique-bucket-name",
acl: "private"
});Key characteristics:
Pulumi uses a special type system to handle asynchronous resource creation:
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/core";
const bucket = new aws.s3.Bucket("my-bucket");
// bucket.arn is an Output<string>
const bucketArn: pulumi.Output<string> = bucket.arn;
// Use .apply() to work with output values
bucket.arn.apply(arn => {
console.log(`Bucket ARN: ${arn}`);
});
// Use outputs as inputs to other resources
const bucketPolicy = new aws.s3.BucketPolicy("my-policy", {
bucket: bucket.id, // Output<string> automatically unwrapped
policy: bucket.arn.apply(arn => JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: "*",
Action: "s3:GetObject",
Resource: `${arn}/*`
}]
}))
});Output types:
Output<T>: Represents a value that will be available after resource creationInput<T>: Accepts either a plain value or an Output<T>.apply() to transform output valuesPulumi maintains state to track the current state of your infrastructure:
State storage:
// State is automatically managed, but you can inspect it
import * as pulumi from "@pulumi/core";
// Export values to make them visible in state
export const bucketName = bucket.id;
export const bucketUrl = pulumi.interpolate`https://${bucket.bucketDomainName}`;State operations:
pulumi up: Creates/updates resources and updates statepulumi refresh: Syncs state with actual infrastructurepulumi destroy: Removes resources and cleans up statepulumi stack export: View/backup stateStacks represent different deployment environments (dev, staging, prod):
import * as pulumi from "@pulumi/core";
// Read configuration values
const config = new pulumi.Config();
const instanceType = config.get("instanceType") || "t3.micro";
const region = config.require("region"); // Throws if not set
const tags = config.requireObject<Record<string, string>>("tags");
// Use configuration in resources
const instance = new aws.ec2.Instance("web-server", {
instanceType: instanceType,
tags: tags
});Configuration management:
pulumi config set instanceType t3.smallpulumi config set --secret dbPassword hunter2pulumi configThe AWS provider manages authentication and default settings:
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/core";
// Default provider (uses AWS credentials from environment)
const bucket = new aws.s3.Bucket("my-bucket");
// Explicit provider with custom configuration
const usWest2Provider = new aws.Provider("us-west-2", {
region: "us-west-2",
profile: "my-aws-profile",
defaultTags: {
tags: {
Environment: "production",
ManagedBy: "pulumi"
}
}
});
// Use explicit provider
const westBucket = new aws.s3.Bucket("west-bucket", {
bucketName: "my-west-bucket"
}, { provider: usWest2Provider });Provider options:
region: AWS region for resourcesprofile: AWS CLI profile to usedefaultTags: Tags applied to all resourcesallowedAccountIds: Restrict to specific AWS accountsassumeRole: Assume an IAM roleAWS resources follow specific naming patterns:
// Logical name vs physical name
const bucket = new aws.s3.Bucket("my-logical-name", {
// Physical name (optional) - must be globally unique
bucketName: "my-company-prod-data-2024"
// If not specified, Pulumi generates: my-logical-name-a1b2c3d
});
// Auto-naming with prefix
const autoNamedBucket = new aws.s3.Bucket("data-bucket", {
// Pulumi will generate: data-bucket-a1b2c3d
});
// Access the actual name
export const actualBucketName = bucket.id;Naming best practices:
Resources are infrastructure objects that Pulumi creates and manages:
// Resources are declared with "new"
const vpc = new aws.ec2.Vpc("my-vpc", {
cidrBlock: "10.0.0.0/16",
enableDnsHostnames: true,
tags: { Name: "my-vpc" }
});
// Pulumi will:
// - Create the VPC on first deployment
// - Update it when properties change
// - Delete it when removed from codeResource characteristics:
Data sources query existing infrastructure without managing it:
// Data sources use get* functions
const defaultVpc = aws.ec2.getVpcOutput({
default: true
});
const ami = aws.ec2.getAmiOutput({
filters: [{
name: "name",
values: ["amzn2-ami-hvm-*-x86_64-gp2"]
}],
owners: ["amazon"],
mostRecent: true
});
// Use data source outputs in resources
const instance = new aws.ec2.Instance("web", {
ami: ami.id,
instanceType: "t3.micro",
vpcSecurityGroupIds: [defaultVpc.apply(v => v.defaultSecurityGroupId)]
});Data source characteristics:
When to use:
Pulumi automatically infers dependencies when outputs are used as inputs:
// Automatic dependency inference
const bucket = new aws.s3.Bucket("my-bucket");
// Policy depends on bucket (inferred from bucket.id usage)
const policy = new aws.s3.BucketPolicy("my-policy", {
bucket: bucket.id,
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: "*",
Action: "s3:GetObject",
Resource: pulumi.interpolate`${bucket.arn}/*`
}]
})
});Sometimes you need to specify dependencies manually:
const cluster = new aws.rds.Cluster("db", {
engine: "aurora-mysql",
masterUsername: "admin",
masterPassword: dbPassword
});
const instance = new aws.rds.ClusterInstance("db-instance", {
clusterIdentifier: cluster.id,
instanceClass: "db.t3.small",
engine: cluster.engine
}, {
// Explicit dependency ensures cluster is fully ready
dependsOn: [cluster]
});Use explicit dependencies when:
Resources can have parent-child relationships for organization:
// Parent resource
const vpc = new aws.ec2.Vpc("my-vpc", {
cidrBlock: "10.0.0.0/16"
});
// Child resources
const subnet1 = new aws.ec2.Subnet("subnet-1", {
vpcId: vpc.id,
cidrBlock: "10.0.1.0/24"
}, { parent: vpc });
const subnet2 = new aws.ec2.Subnet("subnet-2", {
vpcId: vpc.id,
cidrBlock: "10.0.2.0/24"
}, { parent: vpc });
// When parent is deleted, children are deleted firstParent-child benefits:
Pulumi builds a dependency graph to determine execution order:
// Complex dependency example
const vpc = new aws.ec2.Vpc("vpc", { cidrBlock: "10.0.0.0/16" });
const subnet = new aws.ec2.Subnet("subnet", { vpcId: vpc.id, cidrBlock: "10.0.1.0/24" });
const sg = new aws.ec2.SecurityGroup("sg", { vpcId: vpc.id });
const instance = new aws.ec2.Instance("instance", {
subnetId: subnet.id,
vpcSecurityGroupIds: [sg.id],
ami: "ami-12345",
instanceType: "t3.micro"
});
// Execution order: vpc -> (subnet + sg) -> instance
// subnet and sg can be created in parallelGraph properties:
pulumi preview --show-replacement-stepsAll resources accept resource options to control behavior:
const bucket = new aws.s3.Bucket("my-bucket", {
bucketName: "my-unique-bucket"
}, {
// Resource options (third argument)
protect: true, // Prevent accidental deletion
deleteBeforeReplace: true, // Delete before creating replacement
ignoreChanges: ["tags"], // Ignore changes to tags
aliases: ["urn:pulumi:stack::project::aws:s3/bucket:Bucket::old-name"],
dependsOn: [someOtherResource],
provider: customProvider,
parent: parentResource,
retainOnDelete: true, // Keep resource after stack deletion
customTimeouts: {
create: "10m",
update: "10m",
delete: "10m"
}
});Common options:
protect: Prevents deletion (must unset to delete)ignoreChanges: Ignore drift in specified propertiesdeleteBeforeReplace: Useful for name-based resourcesretainOnDelete: Keep resource when removing from PulumicustomTimeouts: Override default operation timeoutsCombine outputs with strings using pulumi.interpolate:
import * as pulumi from "@pulumi/core";
const bucket = new aws.s3.Bucket("my-bucket");
const distribution = new aws.cloudfront.Distribution("cdn", { /* ... */ });
// Interpolate outputs into strings
export const websiteUrl = pulumi.interpolate`https://${distribution.domainName}`;
export const s3Uri = pulumi.interpolate`s3://${bucket.id}/data`;
// Use in resource properties
const policy = new aws.s3.BucketPolicy("policy", {
bucket: bucket.id,
policy: pulumi.interpolate`{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "${bucket.arn}/*"
}]
}`
});Combine multiple outputs:
import * as pulumi from "@pulumi/core";
const bucket1 = new aws.s3.Bucket("bucket-1");
const bucket2 = new aws.s3.Bucket("bucket-2");
const bucket3 = new aws.s3.Bucket("bucket-3");
// Wait for all outputs
const allBuckets = pulumi.all([bucket1.id, bucket2.id, bucket3.id]);
export const bucketList = allBuckets.apply(([id1, id2, id3]) =>
`Buckets: ${id1}, ${id2}, ${id3}`
);
// Use in resources
const policy = new aws.iam.Policy("multi-bucket-policy", {
policy: pulumi.all([bucket1.arn, bucket2.arn, bucket3.arn])
.apply(([arn1, arn2, arn3]) => JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: "s3:GetObject",
Resource: [arn1, arn2, arn3].map(arn => `${arn}/*`)
}]
}))
});Create reusable infrastructure components:
import * as pulumi from "@pulumi/core";
import * as aws from "@pulumi/aws";
// Component resource
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: {
vpcId: pulumi.Input<string>,
subnetId: pulumi.Input<string>,
instanceType?: string
}, opts?: pulumi.ComponentResourceOptions) {
super("custom:WebServer", name, {}, opts);
// Security group
this.securityGroup = new aws.ec2.SecurityGroup(`${name}-sg`, {
vpcId: args.vpcId,
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"] }
],
egress: [
{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] }
]
}, { parent: this });
// EC2 instance
this.instance = new aws.ec2.Instance(`${name}-instance`, {
instanceType: args.instanceType || "t3.micro",
ami: "ami-12345",
subnetId: args.subnetId,
vpcSecurityGroupIds: [this.securityGroup.id]
}, { parent: this });
this.publicIp = this.instance.publicIp;
this.registerOutputs({
instanceId: this.instance.id,
publicIp: this.publicIp
});
}
}
// Usage
const webServer = new WebServer("my-web-server", {
vpcId: vpc.id,
subnetId: subnet.id,
instanceType: "t3.small"
});Create custom resource providers for non-AWS resources or custom logic (advanced):
import * as pulumi from "@pulumi/pulumi";
interface RandomIdInputs {
length: number;
}
class RandomIdProvider implements pulumi.dynamic.ResourceProvider {
async create(inputs: RandomIdInputs): Promise<pulumi.dynamic.CreateResult> {
const id = Math.random().toString(36).substring(2, 2 + inputs.length);
return { id, outs: { result: id, length: inputs.length } };
}
}
class RandomId extends pulumi.dynamic.Resource {
public readonly result!: pulumi.Output<string>;
constructor(name: string, args: RandomIdInputs, opts?: pulumi.CustomResourceOptions) {
super(new RandomIdProvider(), name, { result: undefined, ...args }, opts);
}
}
// Usage
const randomId = new RandomId("my-random-id", { length: 8 });
export const randomValue = randomId.result;dependsOn only when necessarypulumi preview before every deploymentInstall with Tessl CLI
npx tessl i tessl/npm-pulumi--aws