Skip to content

Pulumi — Practical

infra/
Pulumi.yaml
Pulumi.dev.yaml # stack config
Pulumi.prod.yaml
package.json
index.ts
components/
web-app.ts
db.ts
name: platform
runtime: nodejs
description: Platform infra
config:
aws:region: eu-west-1
config:
aws:region: eu-west-1
platform:env: prod
platform:db_size: db.r6g.xlarge
platform:db_password:
secure: AAA...
components/web-app.ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
interface Args {
image: pulumi.Input<string>;
vpcId: pulumi.Input<string>;
subnets: pulumi.Input<string[]>;
}
export class WebApp extends pulumi.ComponentResource {
url: pulumi.Output<string>;
constructor(name: string, args: Args, opts?: pulumi.ComponentResourceOptions) {
super("custom:WebApp", name, {}, opts);
const sg = new aws.ec2.SecurityGroup(`${name}-sg`, {
vpcId: args.vpcId,
ingress: [{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] }],
}, { parent: this });
const lb = new aws.lb.LoadBalancer(`${name}-lb`, {
loadBalancerType: "application",
subnets: args.subnets,
securityGroups: [sg.id],
}, { parent: this });
// ... TG, listener, ECS service, etc.
this.url = pulumi.interpolate`http://${lb.dnsName}`;
this.registerOutputs({ url: this.url });
}
}
index.ts
import * as pulumi from "@pulumi/pulumi";
import { WebApp } from "./components/web-app";
const cfg = new pulumi.Config();
const network = new pulumi.StackReference("acme/network/prod");
const app = new WebApp("api", {
image: cfg.require("image"),
vpcId: network.requireOutput("vpcId"),
subnets: network.requireOutput("publicSubnets") as any,
});
export const apiUrl = app.url;
// interpolation (preferred for strings)
const url = pulumi.interpolate`https://${bucket.bucketDomainName}/${key}`;
// apply for transformation
const upper = bucket.arn.apply(arn => arn.toUpperCase());
// combine multiple
const combined = pulumi.all([bucket.arn, queue.arn])
.apply(([b, q]) => ({ bucket: b, queue: q }));
const dbPassword = cfg.requireSecret("dbPassword");
const db = new aws.rds.Instance("db", {
// dbPassword stays secret in state
password: dbPassword,
...
});
// or wrap explicitly
const apiKey = pulumi.secret("super-key");
Terminal window
pulumi config set --secret dbPassword 'sUp3r$ecret'
const platform = new pulumi.StackReference("acme/platform/prod");
const vpcId = platform.requireOutput("vpcId");
Terminal window
# discover & import
pulumi import aws:s3/bucket:Bucket logs my-existing-bucket
# with options to generate code
pulumi import aws:s3/bucket:Bucket logs my-existing-bucket \
--generate-code --out generated.ts
__tests__/setup.ts
import * as pulumi from "@pulumi/pulumi";
pulumi.runtime.setMocks({
newResource: (args) => ({ id: `${args.name}_id`, state: args.inputs }),
call: (args) => args.inputs,
});
// __tests__/bucket.test.ts
import { describe, it, expect } from "vitest";
import * as infra from "../src";
describe("infra", () => {
it("bucket has encryption", async () => {
const enc = await new Promise(res =>
(infra.bucket.serverSideEncryptionConfiguration as any).apply(res));
expect(enc.rule.applyServerSideEncryptionByDefault.sseAlgorithm).toBe("AES256");
});
});
import * as automation from "@pulumi/pulumi/automation";
const stack = await automation.LocalWorkspace.createOrSelectStack({
stackName: "test-" + Date.now(),
workDir: ".",
});
await stack.setConfig("aws:region", { value: "eu-west-1" });
const up = await stack.up({ onOutput: console.log });
expect(up.outputs.bucketName.value).toContain("logs-");
await stack.destroy();
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE }}
aws-region: eu-west-1
- uses: pulumi/actions@v5
with:
command: preview
stack-name: org/proj/dev
cloud-url: https://app.pulumi.com
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_TOKEN }}
- uses: pulumi/actions@v5
if: github.ref == 'refs/heads/main'
with: { command: up, stack-name: org/proj/dev }
Terminal window
pulumi login s3://my-state-bucket?region=eu-west-1
# secret encryption with KMS
pulumi stack init prod --secrets-provider="awskms://alias/pulumi"
  • Use ComponentResource for any pattern repeated >2x.
  • Tag everything via a default in Pulumi.<stack>.yaml + a wrapper.
  • Output every important id/url/arn → consumed by other stacks via StackReference.
  • Run pulumi preview --diff in PR comment.
  • Keep one stack per environment, not one giant stack with branches.
  • Pulumi Cloud — managed state + audit + RBAC.
  • CrossGuard — policy as code.
  • Automation API — programmatic stack ops.
  • Pulumi ESC — secret/config service.