CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pulumi--aws

A Pulumi package for creating and managing Amazon Web Services (AWS) cloud resources with infrastructure-as-code.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

common-patterns.mddocs/guides/

Common Patterns Guide

This guide covers common patterns and best practices for building AWS infrastructure with Pulumi, including resource tagging, dependency management, and component resources.

Table of Contents

  • Resource Tagging Strategies
  • Dependency Management
  • Output Transformations
  • Cross-Stack References
  • Component Resources
  • Conditional Resource Creation

Resource Tagging Strategies

Basic Tagging

Apply consistent tags to all resources:

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

const commonTags = {
    Project: pulumi.getProject(),
    Stack: pulumi.getStack(),
    Environment: pulumi.getStack(),
    ManagedBy: "pulumi",
};

const bucket = new aws.s3.Bucket("my-bucket", {
    tags: commonTags,
});

const instance = new aws.ec2.Instance("my-instance", {
    instanceType: "t3.micro",
    ami: "ami-0c55b159cbfafe1f0",
    tags: commonTags,
});

Dynamic Tagging with Stack Outputs

Create tags based on stack configuration:

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

const config = new pulumi.Config();
const environment = config.require("environment");
const costCenter = config.require("costCenter");
const owner = config.require("owner");

function getStandardTags(resourceName: string): { [key: string]: string } {
    return {
        Name: resourceName,
        Environment: environment,
        Project: pulumi.getProject(),
        Stack: pulumi.getStack(),
        CostCenter: costCenter,
        Owner: owner,
        ManagedBy: "pulumi",
        CreatedAt: new Date().toISOString(),
    };
}

const vpc = new aws.ec2.Vpc("main-vpc", {
    cidrBlock: "10.0.0.0/16",
    tags: getStandardTags("main-vpc"),
});

const subnet = new aws.ec2.Subnet("public-subnet", {
    vpcId: vpc.id,
    cidrBlock: "10.0.1.0/24",
    tags: {
        ...getStandardTags("public-subnet"),
        Type: "public",
    },
});

Provider-Level Default Tags

Apply default tags to all resources using provider configuration:

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

const provider = new aws.Provider("tagged-provider", {
    region: "us-west-2",
    defaultTags: {
        tags: {
            Environment: pulumi.getStack(),
            Project: pulumi.getProject(),
            ManagedBy: "pulumi",
            Team: "platform",
        },
    },
});

// These resources will automatically inherit default tags
const bucket = new aws.s3.Bucket("auto-tagged-bucket", {
    tags: {
        Purpose: "storage", // Merged with default tags
    },
}, { provider });

const table = new aws.dynamodb.Table("auto-tagged-table", {
    attributes: [{ name: "id", type: "S" }],
    hashKey: "id",
    billingMode: "PAY_PER_REQUEST",
    tags: {
        DataType: "user-data",
    },
}, { provider });

Tag-Based Resource Discovery

Use tags for resource discovery and management:

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

// Create resources with discovery tags
const productionResources = ["api", "web", "worker"].map(service => {
    return new aws.ec2.Instance(`prod-${service}`, {
        instanceType: "t3.medium",
        ami: "ami-0c55b159cbfafe1f0",
        tags: {
            Environment: "production",
            Service: service,
            Discoverable: "true",
            AutoShutdown: "false",
        },
    });
});

// Query resources by tags
const discoverableInstances = aws.ec2.getInstancesOutput({
    filters: [
        { name: "tag:Discoverable", values: ["true"] },
        { name: "tag:Environment", values: ["production"] },
        { name: "instance-state-name", values: ["running"] },
    ],
});

export const discoverableInstanceIds = discoverableInstances.ids;

Dependency Management

Explicit Dependencies

Use dependsOn for explicit resource ordering:

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

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

const igw = new aws.ec2.InternetGateway("igw", {
    vpcId: vpc.id,
});

const routeTable = new aws.ec2.RouteTable("public-rt", {
    vpcId: vpc.id,
});

// Ensure IGW exists before creating route
const route = new aws.ec2.Route("public-route", {
    routeTableId: routeTable.id,
    destinationCidrBlock: "0.0.0.0/0",
    gatewayId: igw.id,
}, {
    dependsOn: [igw],
});

Parent-Child Relationships

Use parent option for logical grouping and cascade deletion:

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",
});

// Subnets are children of VPC
const publicSubnet = new aws.ec2.Subnet("public-subnet", {
    vpcId: vpc.id,
    cidrBlock: "10.0.1.0/24",
}, {
    parent: vpc,
});

const privateSubnet = new aws.ec2.Subnet("private-subnet", {
    vpcId: vpc.id,
    cidrBlock: "10.0.2.0/24",
}, {
    parent: vpc,
});

// Deleting VPC will cascade delete subnets

Dependency Chains

Handle complex dependency chains:

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

// 1. Create VPC
const vpc = new aws.ec2.Vpc("vpc", {
    cidrBlock: "10.0.0.0/16",
});

// 2. Create subnet (depends on VPC)
const subnet = new aws.ec2.Subnet("subnet", {
    vpcId: vpc.id,
    cidrBlock: "10.0.1.0/24",
});

// 3. Create security group (depends on VPC)
const sg = new aws.ec2.SecurityGroup("sg", {
    vpcId: vpc.id,
    ingress: [{
        protocol: "tcp",
        fromPort: 80,
        toPort: 80,
        cidrBlocks: ["0.0.0.0/0"],
    }],
});

// 4. Create instance (depends on subnet and security group)
const instance = new aws.ec2.Instance("instance", {
    instanceType: "t3.micro",
    ami: "ami-0c55b159cbfafe1f0",
    subnetId: subnet.id,
    vpcSecurityGroupIds: [sg.id],
});

// 5. Create Elastic IP (depends on instance)
const eip = new aws.ec2.Eip("eip", {
    instance: instance.id,
    vpc: true,
}, {
    dependsOn: [instance],
});

Protecting Dependencies

Ensure critical resources are not accidentally deleted:

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

// Production database - protect from deletion
const database = new aws.rds.Instance("prod-db", {
    engine: "postgres",
    instanceClass: "db.t3.medium",
    allocatedStorage: 100,
    username: "admin",
    password: pulumi.secret("super-secret-password"),
}, {
    protect: true, // Requires explicit unprotect before deletion
});

// Application depends on database
const app = new aws.ecs.Service("app-service", {
    // ... service configuration
}, {
    dependsOn: [database],
});

Output Transformations

Apply Method

Transform outputs using the apply method:

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

const bucket = new aws.s3.Bucket("my-bucket");

// Transform bucket name to uppercase
export const bucketNameUpper = bucket.bucket.apply(name => name.toUpperCase());

// Construct ARN from bucket name
export const bucketArn = bucket.bucket.apply(name => `arn:aws:s3:::${name}`);

// Conditional transformation
export const bucketUrl = bucket.bucket.apply(name =>
    name.startsWith("public-") ? `https://${name}.s3.amazonaws.com` : null
);

All Method for Multiple Outputs

Combine multiple outputs:

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

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 instance = new aws.ec2.Instance("instance", {
    instanceType: "t3.micro",
    ami: "ami-0c55b159cbfafe1f0",
    subnetId: subnet.id,
});

// Combine multiple outputs
export const instanceInfo = pulumi.all([
    instance.id,
    instance.publicIp,
    instance.privateIp,
    vpc.id,
]).apply(([id, publicIp, privateIp, vpcId]) => ({
    instanceId: id,
    publicEndpoint: `http://${publicIp}`,
    privateEndpoint: `http://${privateIp}`,
    vpcId: vpcId,
}));

Interpolation

Use pulumi.interpolate for string formatting with outputs:

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

const cluster = new aws.ecs.Cluster("app-cluster");
const loadBalancer = new aws.lb.LoadBalancer("app-lb", {
    loadBalancerType: "application",
    subnets: ["subnet-1", "subnet-2"],
});

// Create connection string with interpolation
export const clusterEndpoint = pulumi.interpolate`${cluster.name}.${loadBalancer.dnsName}`;

// Create policy document with interpolation
const bucket = new aws.s3.Bucket("data-bucket");
const bucketPolicy = new aws.s3.BucketPolicy("data-bucket-policy", {
    bucket: bucket.id,
    policy: pulumi.interpolate`{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "${bucket.arn}/*"
        }]
    }`,
});

Async Transformations

Perform async operations in transformations:

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

const instance = new aws.ec2.Instance("web-instance", {
    instanceType: "t3.micro",
    ami: "ami-0c55b159cbfafe1f0",
});

// Async lookup based on instance output
export const instanceAmiDetails = instance.ami.apply(async amiId => {
    const ami = await aws.ec2.getAmi({
        filters: [{ name: "image-id", values: [amiId] }],
    });
    return {
        name: ami.name,
        description: ami.description,
        creationDate: ami.creationDate,
    };
});

Cross-Stack References

Stack Reference Basic

Reference outputs from another stack:

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

// In network stack (foundation/network)
const vpc = new aws.ec2.Vpc("shared-vpc", {
    cidrBlock: "10.0.0.0/16",
});

export const vpcId = vpc.id;
export const publicSubnetIds = ["subnet-1", "subnet-2"];

// In application stack (app/production)
const networkStack = new pulumi.StackReference("network", {
    name: "organization/foundation/network",
});

const vpcId = networkStack.requireOutput("vpcId");
const subnetIds = networkStack.requireOutput("publicSubnetIds");

const instance = new aws.ec2.Instance("app-instance", {
    instanceType: "t3.micro",
    ami: "ami-0c55b159cbfafe1f0",
    subnetId: subnetIds.apply(ids => ids[0]),
});

Cross-Stack with Type Safety

Add type safety to stack references:

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

interface NetworkStackOutputs {
    vpcId: pulumi.Output<string>;
    publicSubnetIds: pulumi.Output<string[]>;
    privateSubnetIds: pulumi.Output<string[]>;
    securityGroupId: pulumi.Output<string>;
}

function getNetworkStack(): NetworkStackOutputs {
    const stack = new pulumi.StackReference("network", {
        name: `${pulumi.getOrganization()}/network/${pulumi.getStack()}`,
    });

    return {
        vpcId: stack.requireOutput("vpcId"),
        publicSubnetIds: stack.requireOutput("publicSubnetIds"),
        privateSubnetIds: stack.requireOutput("privateSubnetIds"),
        securityGroupId: stack.requireOutput("securityGroupId"),
    };
}

const network = getNetworkStack();

const alb = new aws.lb.LoadBalancer("app-alb", {
    loadBalancerType: "application",
    subnets: network.publicSubnetIds,
    securityGroups: [network.securityGroupId],
});

Multi-Stack Dependencies

Reference multiple stacks:

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

// Reference network stack
const networkStack = new pulumi.StackReference("network", {
    name: "org/network/prod",
});

// Reference data stack
const dataStack = new pulumi.StackReference("data", {
    name: "org/data/prod",
});

// Reference security stack
const securityStack = new pulumi.StackReference("security", {
    name: "org/security/prod",
});

const application = new aws.ecs.TaskDefinition("app-task", {
    family: "app",
    cpu: "256",
    memory: "512",
    containerDefinitions: pulumi.all([
        dataStack.requireOutput("databaseEndpoint"),
        securityStack.requireOutput("appSecretArn"),
    ]).apply(([dbEndpoint, secretArn]) => JSON.stringify([{
        name: "app",
        image: "nginx:latest",
        environment: [
            { name: "DB_ENDPOINT", value: dbEndpoint },
        ],
        secrets: [
            { name: "DB_PASSWORD", valueFrom: secretArn },
        ],
    }])),
});

Component Resources

Basic Component

Create reusable component resources:

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

interface WebServerArgs {
    instanceType: string;
    keyName: string;
    vpcId: pulumi.Input<string>;
    subnetId: pulumi.Input<string>;
}

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:app:WebServer", name, {}, opts);

        // Create 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 });

        // Create instance
        this.instance = new aws.ec2.Instance(`${name}-instance`, {
            instanceType: args.instanceType,
            ami: "ami-0c55b159cbfafe1f0",
            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`,
        }, { parent: this });

        this.publicIp = this.instance.publicIp;

        this.registerOutputs({
            instance: this.instance,
            securityGroup: this.securityGroup,
            publicIp: this.publicIp,
        });
    }
}

// Use the component
const webServer = new WebServer("my-web-server", {
    instanceType: "t3.small",
    keyName: "my-key",
    vpcId: "vpc-12345",
    subnetId: "subnet-12345",
});

export const webServerIp = webServer.publicIp;

Advanced Component with Multiple Resources

Build complex components:

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

interface StaticWebsiteArgs {
    domain: string;
    certificateArn?: pulumi.Input<string>;
    errorDocument?: string;
    indexDocument?: string;
}

class StaticWebsite extends pulumi.ComponentResource {
    public readonly bucket: aws.s3.Bucket;
    public readonly bucketPolicy: aws.s3.BucketPolicy;
    public readonly distribution: aws.cloudfront.Distribution;
    public readonly websiteUrl: pulumi.Output<string>;

    constructor(name: string, args: StaticWebsiteArgs, opts?: pulumi.ComponentResourceOptions) {
        super("custom:web:StaticWebsite", name, {}, opts);

        // S3 bucket for website content
        this.bucket = new aws.s3.Bucket(`${name}-bucket`, {
            website: {
                indexDocument: args.indexDocument || "index.html",
                errorDocument: args.errorDocument || "404.html",
            },
        }, { parent: this });

        // Bucket policy for public read
        this.bucketPolicy = new aws.s3.BucketPolicy(`${name}-policy`, {
            bucket: this.bucket.id,
            policy: this.bucket.arn.apply(arn => JSON.stringify({
                Version: "2012-10-17",
                Statement: [{
                    Effect: "Allow",
                    Principal: "*",
                    Action: "s3:GetObject",
                    Resource: `${arn}/*`,
                }],
            })),
        }, { parent: this });

        // CloudFront distribution
        this.distribution = new aws.cloudfront.Distribution(`${name}-cdn`, {
            enabled: true,
            aliases: [args.domain],
            origins: [{
                originId: this.bucket.arn,
                domainName: this.bucket.websiteEndpoint,
                customOriginConfig: {
                    originProtocolPolicy: "http-only",
                    httpPort: 80,
                    httpsPort: 443,
                    originSslProtocols: ["TLSv1.2"],
                },
            }],
            defaultCacheBehavior: {
                targetOriginId: this.bucket.arn,
                viewerProtocolPolicy: "redirect-to-https",
                allowedMethods: ["GET", "HEAD", "OPTIONS"],
                cachedMethods: ["GET", "HEAD"],
                forwardedValues: {
                    queryString: false,
                    cookies: { forward: "none" },
                },
            },
            viewerCertificate: args.certificateArn ? {
                acmCertificateArn: args.certificateArn,
                sslSupportMethod: "sni-only",
            } : {
                cloudfrontDefaultCertificate: true,
            },
            restrictions: {
                geoRestriction: {
                    restrictionType: "none",
                },
            },
        }, { parent: this });

        this.websiteUrl = this.distribution.domainName.apply(domain => `https://${domain}`);

        this.registerOutputs({
            bucket: this.bucket,
            distribution: this.distribution,
            websiteUrl: this.websiteUrl,
        });
    }
}

// Deploy static website
const website = new StaticWebsite("company-site", {
    domain: "www.example.com",
    certificateArn: "arn:aws:acm:us-east-1:123456789012:certificate/12345678",
});

export const websiteUrl = website.websiteUrl;

Conditional Resource Creation

Using Config

Create resources conditionally based on configuration:

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

const config = new pulumi.Config();
const enableBackups = config.getBoolean("enableBackups") || false;
const enableMonitoring = config.getBoolean("enableMonitoring") || false;

const bucket = new aws.s3.Bucket("data-bucket");

// Conditionally create backup bucket
const backupBucket = enableBackups ? new aws.s3.Bucket("backup-bucket", {
    versioningEnabled: true,
    lifecycleRules: [{
        enabled: true,
        transitions: [{
            days: 30,
            storageClass: "GLACIER",
        }],
    }],
}) : undefined;

// Conditionally create CloudWatch alarm
const alarm = enableMonitoring ? new aws.cloudwatch.MetricAlarm("bucket-alarm", {
    comparisonOperator: "GreaterThanThreshold",
    evaluationPeriods: 2,
    metricName: "NumberOfObjects",
    namespace: "AWS/S3",
    period: 300,
    statistic: "Average",
    threshold: 1000,
    dimensions: {
        BucketName: bucket.bucket,
    },
}) : undefined;

export const hasBackups = enableBackups;
export const hasMonitoring = enableMonitoring;

Stack-Based Conditionals

Different resources for different stacks:

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

const stack = pulumi.getStack();
const isProduction = stack === "production";

// Production gets larger instances
const instanceType = isProduction ? "t3.large" : "t3.micro";
const instanceCount = isProduction ? 3 : 1;

// Production gets Multi-AZ database
const database = new aws.rds.Instance("app-db", {
    engine: "postgres",
    instanceClass: isProduction ? "db.t3.large" : "db.t3.micro",
    allocatedStorage: isProduction ? 100 : 20,
    multiAz: isProduction,
    backupRetentionPeriod: isProduction ? 7 : 1,
    username: "admin",
    password: pulumi.secret("db-password"),
});

// Create multiple instances for production
const instances = Array.from({ length: instanceCount }, (_, i) => {
    return new aws.ec2.Instance(`app-instance-${i}`, {
        instanceType: instanceType,
        ami: "ami-0c55b159cbfafe1f0",
        tags: {
            Name: `app-instance-${i}`,
            Environment: stack,
        },
    });
});

// Production gets ALB, dev gets single instance
const loadBalancer = isProduction ? new aws.lb.LoadBalancer("app-alb", {
    loadBalancerType: "application",
    subnets: ["subnet-1", "subnet-2"],
}) : undefined;

export const endpoint = isProduction
    ? loadBalancer!.dnsName
    : instances[0].publicIp;

Feature Flags

Use feature flags for gradual rollouts:

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

const config = new pulumi.Config();

interface FeatureFlags {
    enableNewApi: boolean;
    enableCache: boolean;
    enableMetrics: boolean;
    enableWaf: boolean;
}

const features: FeatureFlags = {
    enableNewApi: config.getBoolean("feature:newApi") || false,
    enableCache: config.getBoolean("feature:cache") || false,
    enableMetrics: config.getBoolean("feature:metrics") || false,
    enableWaf: config.getBoolean("feature:waf") || false,
};

// Conditionally create API Gateway v2
const api = features.enableNewApi
    ? new aws.apigatewayv2.Api("api-v2", {
        protocolType: "HTTP",
    })
    : new aws.apigateway.RestApi("api-v1", {
        description: "Legacy API",
    });

// Conditionally create ElastiCache
const cache = features.enableCache ? new aws.elasticache.Cluster("cache", {
    engine: "redis",
    nodeType: "cache.t3.micro",
    numCacheNodes: 1,
}) : undefined;

// Conditionally enable detailed monitoring
const instance = new aws.ec2.Instance("app", {
    instanceType: "t3.micro",
    ami: "ami-0c55b159cbfafe1f0",
    monitoring: features.enableMetrics,
});

// Conditionally add WAF
const waf = features.enableWaf ? new aws.wafv2.WebAcl("waf", {
    scope: "REGIONAL",
    defaultAction: { allow: {} },
    rules: [{
        name: "rate-limit",
        priority: 1,
        statement: {
            rateBasedStatement: {
                limit: 2000,
                aggregateKeyType: "IP",
            },
        },
        action: { block: {} },
        visibilityConfig: {
            sampledRequestsEnabled: true,
            cloudwatchMetricsEnabled: true,
            metricName: "rate-limit",
        },
    }],
    visibilityConfig: {
        sampledRequestsEnabled: true,
        cloudwatchMetricsEnabled: true,
        metricName: "waf",
    },
}) : undefined;

export const activeFeatures = features;

Install with Tessl CLI

npx tessl i tessl/npm-pulumi--aws

docs

index.md

quickstart.md

README.md

tile.json