When a Pulumi Component Beats Copy-Paste: Building a Reusable VPC with Crosswalk for AWS
Stop copying VPC stacks between environments. Pulumi's Crosswalk for AWS lets you define a network once as a versioned component — with real trade-offs around NAT cost and abstraction limits.
04 Jul 2025, 20:47 UTC

The copy-paste problem
Every team that adopts infrastructure as code eventually hits the same wall: the third environment needs the same VPC, the same subnets, the same NAT gateways, and someone copies the first stack, tweaks a CIDR block, and quietly introduces a drift that nobody notices until routing breaks in staging. The takeaway here is simple: Pulumi lets you treat that repeated infrastructure as a versioned software component, and for AWS specifically, the Crosswalk library gives you a well-tested starting point so you are not hand-rolling subnet math at all.
Crosswalk for AWS is a set of higher-level Pulumi components (written in TypeScript) that encode sensible defaults for common AWS patterns: VPCs with public and private subnets spread across availability zones, ECS services with load balancing, IAM roles, and more. You still write ordinary code, but you compose from building blocks instead of wiring thirty raw resources together.
A concrete example: one VPC, many environments
Assume Pulumi CLI 3.x, Node.js 18+, and AWS credentials configured via the standard provider chain (environment variables or an AWS profile). Create a project with pulumi new aws-typescript, then install the AWSX package (the Crosswalk library for AWS):
npm install @pulumi/awsx @pulumi/awsReplace the contents of index.ts with a small component that wraps an AWSX VPC:
import * as pulumi from "@pulumi/pulumi";
import * as awsx from "@pulumi/awsx";
const config = new pulumi.Config();
const azCount = config.getNumber("azCount") ?? 2;
const vpc = new awsx.ec2.Vpc("app-vpc", {
numberOfAvailabilityZones: azCount,
natGateways: { strategy: "Single" }, // one NAT gateway, cheaper for non-prod
tags: { Environment: pulumi.getStack() },
});
export const vpcId = vpc.vpcId;
export const privateSubnetIds = vpc.privateSubnetIds;
export const publicSubnetIds = vpc.publicSubnetIds;Run pulumi preview (read-only, safe) to inspect the plan, then pulumi up to apply. You need IAM permissions for EC2 VPC, subnet, route table, NAT gateway, and EIP creation. The preview should show a VPC, subnets across two AZs, route tables, an internet gateway, and one NAT gateway — roughly a dozen resources from five lines of intent.
Why this beats raw resources for this job
The raw aws.ec2.Vpc plus aws.ec2.Subnet route is fine when you need unusual topology, but for the standard "public subnets for load balancers, private subnets for workloads" pattern, Crosswalk handles the tedious parts: CIDR allocation across AZs, route table associations, and tagging. Because it is just a Pulumi ComponentResource, you can also wrap it in your own component class, publish it to a private npm registry, and let every team consume new ourcompany.StandardVpc(...) with enforced tagging and NAT strategy baked in. That is the real engineering decision: you are not just provisioning a VPC, you are defining the only way VPCs get made in your organization.
Trade-offs worth knowing
First, opinionated defaults cut both ways. The Single NAT gateway strategy above saves money but is a single point of failure — for production you would use OnePerAz, which multiplies NAT gateway and EIP costs. Second, Crosswalk abstracts away resource names, which makes targeted changes (say, importing an existing subnet into the component) harder than with flat resources; you may need transformations or to drop to raw resources for edge cases. Third, the AWSX package version matters: the 1.x to 2.x line changed the VPC API, so pin the version in package.json and check the changelog before upgrading.
To verify the result after pulumi up, run aws ec2 describe-vpcs --vpc-ids $(pulumi stack output vpcId) with read-only EC2 permissions and confirm the subnet count and tags match your stack outputs. Drift between the plan and reality will also surface on the next pulumi preview if someone modified the VPC in the console.
Where to go from here
Start by replacing one copied-and-pasted network stack with an AWSX VPC component, export the subnet IDs, and point an existing workload stack at them via stack references. Once that works, promote your wrapper into a shared package and make the copy-paste version the exception rather than the rule.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.