Provisioning a GCP VPC with Pulumi
Learn how to build a custom-mode VPC with public and private subnets and firewall rules using Pulumi and TypeScript
Provisioning a GCP VPC with Pulumi
This guide builds the same VPC topology as the Terraform VPC guide on this site, using Pulumi and @pulumi/gcp - a custom-mode network with public and private subnets and the firewall rules the rest of this GCP Pulumi series builds on.
Prerequisites
npm install @pulumi/pulumi @pulumi/gcp
- Pulumi CLI and
gcloud auth application-default logincompleted pulumi config set gcp:project <your-project-id>
Custom-Mode Network and Subnets
GCP networks are either “auto” mode (a subnet is created automatically in every region) or “custom” mode (you define exactly which subnets exist, where) - autoCreateSubnetworks: false is what selects custom mode, the same choice the Terraform VPC guide makes and the one production setups almost always want, since auto mode creates a subnet in every GCP region whether you use it or not:
// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const config = new pulumi.Config();
const region = config.get("region") ?? "us-central1";
const network = new gcp.compute.Network("main", {
name: "app-vpc",
autoCreateSubnetworks: false,
});
const publicSubnet = new gcp.compute.Subnetwork("public", {
name: "public-subnet",
network: network.id,
ipCidrRange: "10.0.1.0/24",
region: region,
});
const privateSubnet = new gcp.compute.Subnetwork("private", {
name: "private-subnet",
network: network.id,
ipCidrRange: "10.0.2.0/24",
region: region,
privateIpGoogleAccess: true,
});
privateIpGoogleAccess: true on the private subnet lets instances with no external IP still reach Google APIs (Cloud Storage, Cloud Functions, and so on) over Google’s internal network - without it, an instance with no public IP has no route to any Google service either.
Firewall Rules
Unlike AWS/Azure security groups, which are attached to specific resources, GCP firewall rules apply network-wide and use targetTags/sourceRanges to scope which instances and traffic they affect - closer in spirit to a shared, tag-matched policy than a per-resource attachment:
const allowHttps = new gcp.compute.Firewall("allow-https", {
name: "allow-https",
network: network.id,
allows: [{
protocol: "tcp",
ports: ["443"],
}],
sourceRanges: ["0.0.0.0/0"],
targetTags: ["https-server"],
});
const allowInternal = new gcp.compute.Firewall("allow-internal", {
name: "allow-internal",
network: network.id,
allows: [{
protocol: "tcp",
ports: ["0-65535"],
}],
sourceRanges: ["10.0.0.0/16"],
});
allowHttps only applies to instances tagged https-server (see the Compute Engine guide) even though its sourceRanges is open to the internet - the tag match is what narrows it, not the source range. allowInternal has no targetTags, so it applies to every instance on the network, letting anything inside the VPC’s own CIDR talk to anything else on any port.
Stack Outputs
export const networkId = network.id;
export const publicSubnetId = publicSubnet.id;
export const privateSubnetId = privateSubnet.id;
Best Practices
- Always use custom-mode networks (
autoCreateSubnetworks: false) for anything beyond a throwaway experiment - auto mode’s one-subnet-per-region default is almost never the topology you actually want. - Scope firewall rules with
targetTags, not justsourceRanges- a rule with no target tag applies to every instance on the network, which is rarely the intent. - Enable
privateIpGoogleAccesson any subnet holding instances without public IPs that still need to call Google APIs (Cloud Storage, Pub/Sub, Cloud Functions) - it’s easy to forget and the failure mode (API calls timing out) doesn’t obviously point back to this setting.
Conclusion
This VPC is the foundation the rest of this GCP Pulumi series builds on - the Compute Engine and GKE guides both reference network, publicSubnet, and privateSubnet from here.
For more Pulumi topics, check out: