Provisioning GCP Compute Engine with Pulumi
Learn how to launch a Compute Engine VM and a Managed Instance Group with Pulumi and TypeScript
Provisioning GCP Compute Engine with Pulumi
This guide launches a Compute Engine VM with Pulumi, building on the VPC guide - a single instance with SSH key metadata, then the Instance Template + Managed Instance Group pattern for anything beyond one box.
A Single Instance
// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const config = new pulumi.Config();
const sshPublicKey = config.require("sshPublicKey");
const webInstance = new gcp.compute.Instance("web", {
name: "web-instance",
machineType: "e2-medium",
zone: `${region}-a`,
tags: ["https-server"],
bootDisk: {
initializeParams: {
image: "debian-cloud/debian-12",
},
},
networkInterfaces: [{
network: network.id,
subnetwork: publicSubnet.id,
accessConfigs: [{}],
}],
metadata: {
"ssh-keys": `debian:${sshPublicKey}`,
},
});
tags: ["https-server"] is what makes the allow-https firewall rule from the VPC guide apply to this instance - GCP firewall tags and Compute Engine instance tags are the same tag namespace, matched by string equality. accessConfigs: [{}] (an empty object, not omitted) is what actually gives the instance an ephemeral public IP - a networkInterfaces entry with no accessConfigs at all gets no external IP, which is the setting you’d use for anything in the private subnet instead.
metadata["ssh-keys"] in the username:public-key format is how Compute Engine’s guest agent provisions SSH access - there’s no separate key-pair resource to create and reference the way AWS’s aws_key_pair or Azure’s VM linuxConfiguration.ssh block work; the key is just instance metadata.
Reading webInstance’s assigned public IP back out requires .apply() since accessConfigs is typed as possibly undefined (an instance might have none):
export const instancePublicIp = webInstance.networkInterfaces.apply(
nics => nics[0].accessConfigs?.[0]?.natIp
);
Instance Template and Managed Instance Group
For anything beyond a single instance, define an InstanceTemplate once and let a InstanceGroupManager stamp out and self-heal instances from it - GCP’s equivalent of an AWS Launch Template + Auto Scaling Group:
const instanceTemplate = new gcp.compute.InstanceTemplate("web", {
name: "web-template",
machineType: "e2-medium",
tags: ["https-server"],
disks: [{
sourceImage: "debian-cloud/debian-12",
autoDelete: true,
boot: true,
}],
networkInterfaces: [{
network: network.id,
subnetwork: publicSubnet.id,
accessConfigs: [{}],
}],
metadata: {
"ssh-keys": `debian:${sshPublicKey}`,
},
});
const mig = new gcp.compute.InstanceGroupManager("web", {
name: "web-mig",
zone: `${region}-a`,
baseInstanceName: "web",
targetSize: 2,
versions: [{
instanceTemplate: instanceTemplate.id,
}],
});
Note the field name difference between the two resources for the same concept: InstanceTemplate calls it disks (with sourceImage/boot/autoDelete per disk), while the standalone Instance above calls the same thing bootDisk (singular, with initializeParams.image) - both are real, current, correctly-typed arguments, just named differently because they’re separate resource schemas.
Best Practices
- Use an Instance Template + Managed Instance Group for anything customer-facing, the same reasoning as an ASG on AWS or a scale set on Azure - a standalone
Instancehas no self-healing if it or its zone fails. - Tag instances deliberately and match firewall rules to those tags rather than widening
sourceRanges- the tag is the actual access-control boundary here, not the CIDR. - Use
debian-cloud/debian-12or another current LTS image family, not a pinned specific image name - image families always resolve to the latest patched image in that family at deploy time.
Conclusion
Compare this to the EC2 guide: the Instance Template + Managed Instance Group pattern here is functionally the same idea as a Launch Template + Auto Scaling Group, just with GCP’s own field names and its tag-based (rather than security-group-based) firewall model.
For more Pulumi topics, check out: