Create and Configure Compute Engine Instances

The gcp:compute/instance:Instance resource, part of the Pulumi GCP provider, provisions a GCE VM instance: its machine type, boot disk, network placement, and security features. This guide focuses on two capabilities: basic VM configuration with service accounts and confidential computing for memory encryption.

A GCE instance doesn’t exist in isolation. It requires a boot disk image, network configuration, and service account for GCP service access. The examples assume the default VPC exists and reference service accounts created separately. They’re intentionally small demonstrations of instance-level features rather than complete infrastructure stacks.

Launch an instance with service account and startup script

Most deployments start by defining a VM with a boot disk, machine type, and network configuration. Service accounts grant permissions to interact with GCP services, while startup scripts automate initial setup tasks.

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

const _default = new gcp.serviceaccount.Account("default", {
    accountId: "my-custom-sa",
    displayName: "Custom SA for VM Instance",
});
const defaultInstance = new gcp.compute.Instance("default", {
    networkInterfaces: [{
        accessConfigs: [{}],
        network: "default",
    }],
    name: "my-instance",
    machineType: "n2-standard-2",
    zone: "us-central1-a",
    tags: [
        "foo",
        "bar",
    ],
    bootDisk: {
        initializeParams: {
            image: "debian-cloud/debian-11",
            labels: {
                my_label: "value",
            },
        },
    },
    scratchDisks: [{
        "interface": "NVME",
    }],
    metadata: {
        foo: "bar",
    },
    metadataStartupScript: "echo hi > /test.txt",
    serviceAccount: {
        email: _default.email,
        scopes: ["cloud-platform"],
    },
});
import pulumi
import pulumi_gcp as gcp

default = gcp.serviceaccount.Account("default",
    account_id="my-custom-sa",
    display_name="Custom SA for VM Instance")
default_instance = gcp.compute.Instance("default",
    network_interfaces=[{
        "access_configs": [{}],
        "network": "default",
    }],
    name="my-instance",
    machine_type="n2-standard-2",
    zone="us-central1-a",
    tags=[
        "foo",
        "bar",
    ],
    boot_disk={
        "initialize_params": {
            "image": "debian-cloud/debian-11",
            "labels": {
                "my_label": "value",
            },
        },
    },
    scratch_disks=[{
        "interface": "NVME",
    }],
    metadata={
        "foo": "bar",
    },
    metadata_startup_script="echo hi > /test.txt",
    service_account={
        "email": default.email,
        "scopes": ["cloud-platform"],
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_default, err := serviceaccount.NewAccount(ctx, "default", &serviceaccount.AccountArgs{
			AccountId:   pulumi.String("my-custom-sa"),
			DisplayName: pulumi.String("Custom SA for VM Instance"),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewInstance(ctx, "default", &compute.InstanceArgs{
			NetworkInterfaces: compute.InstanceNetworkInterfaceArray{
				&compute.InstanceNetworkInterfaceArgs{
					AccessConfigs: compute.InstanceNetworkInterfaceAccessConfigArray{
						&compute.InstanceNetworkInterfaceAccessConfigArgs{},
					},
					Network: pulumi.String("default"),
				},
			},
			Name:        pulumi.String("my-instance"),
			MachineType: pulumi.String("n2-standard-2"),
			Zone:        pulumi.String("us-central1-a"),
			Tags: pulumi.StringArray{
				pulumi.String("foo"),
				pulumi.String("bar"),
			},
			BootDisk: &compute.InstanceBootDiskArgs{
				InitializeParams: &compute.InstanceBootDiskInitializeParamsArgs{
					Image: pulumi.String("debian-cloud/debian-11"),
					Labels: pulumi.StringMap{
						"my_label": pulumi.String("value"),
					},
				},
			},
			ScratchDisks: compute.InstanceScratchDiskArray{
				&compute.InstanceScratchDiskArgs{
					Interface: pulumi.String("NVME"),
				},
			},
			Metadata: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
			MetadataStartupScript: pulumi.String("echo hi > /test.txt"),
			ServiceAccount: &compute.InstanceServiceAccountArgs{
				Email: _default.Email,
				Scopes: pulumi.StringArray{
					pulumi.String("cloud-platform"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var @default = new Gcp.ServiceAccount.Account("default", new()
    {
        AccountId = "my-custom-sa",
        DisplayName = "Custom SA for VM Instance",
    });

    var defaultInstance = new Gcp.Compute.Instance("default", new()
    {
        NetworkInterfaces = new[]
        {
            new Gcp.Compute.Inputs.InstanceNetworkInterfaceArgs
            {
                AccessConfigs = new[]
                {
                    null,
                },
                Network = "default",
            },
        },
        Name = "my-instance",
        MachineType = "n2-standard-2",
        Zone = "us-central1-a",
        Tags = new[]
        {
            "foo",
            "bar",
        },
        BootDisk = new Gcp.Compute.Inputs.InstanceBootDiskArgs
        {
            InitializeParams = new Gcp.Compute.Inputs.InstanceBootDiskInitializeParamsArgs
            {
                Image = "debian-cloud/debian-11",
                Labels = 
                {
                    { "my_label", "value" },
                },
            },
        },
        ScratchDisks = new[]
        {
            new Gcp.Compute.Inputs.InstanceScratchDiskArgs
            {
                Interface = "NVME",
            },
        },
        Metadata = 
        {
            { "foo", "bar" },
        },
        MetadataStartupScript = "echo hi > /test.txt",
        ServiceAccount = new Gcp.Compute.Inputs.InstanceServiceAccountArgs
        {
            Email = @default.Email,
            Scopes = new[]
            {
                "cloud-platform",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.compute.Instance;
import com.pulumi.gcp.compute.InstanceArgs;
import com.pulumi.gcp.compute.inputs.InstanceNetworkInterfaceArgs;
import com.pulumi.gcp.compute.inputs.InstanceBootDiskArgs;
import com.pulumi.gcp.compute.inputs.InstanceBootDiskInitializeParamsArgs;
import com.pulumi.gcp.compute.inputs.InstanceScratchDiskArgs;
import com.pulumi.gcp.compute.inputs.InstanceServiceAccountArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var default_ = new Account("default", AccountArgs.builder()
            .accountId("my-custom-sa")
            .displayName("Custom SA for VM Instance")
            .build());

        var defaultInstance = new Instance("defaultInstance", InstanceArgs.builder()
            .networkInterfaces(InstanceNetworkInterfaceArgs.builder()
                .accessConfigs(InstanceNetworkInterfaceAccessConfigArgs.builder()
                    .build())
                .network("default")
                .build())
            .name("my-instance")
            .machineType("n2-standard-2")
            .zone("us-central1-a")
            .tags(            
                "foo",
                "bar")
            .bootDisk(InstanceBootDiskArgs.builder()
                .initializeParams(InstanceBootDiskInitializeParamsArgs.builder()
                    .image("debian-cloud/debian-11")
                    .labels(Map.of("my_label", "value"))
                    .build())
                .build())
            .scratchDisks(InstanceScratchDiskArgs.builder()
                .interface_("NVME")
                .build())
            .metadata(Map.of("foo", "bar"))
            .metadataStartupScript("echo hi > /test.txt")
            .serviceAccount(InstanceServiceAccountArgs.builder()
                .email(default_.email())
                .scopes("cloud-platform")
                .build())
            .build());

    }
}
resources:
  default:
    type: gcp:serviceaccount:Account
    properties:
      accountId: my-custom-sa
      displayName: Custom SA for VM Instance
  defaultInstance:
    type: gcp:compute:Instance
    name: default
    properties:
      networkInterfaces:
        - accessConfigs:
            - {}
          network: default
      name: my-instance
      machineType: n2-standard-2
      zone: us-central1-a
      tags:
        - foo
        - bar
      bootDisk:
        initializeParams:
          image: debian-cloud/debian-11
          labels:
            my_label: value
      scratchDisks:
        - interface: NVME
      metadata:
        foo: bar
      metadataStartupScript: echo hi > /test.txt
      serviceAccount:
        email: ${default.email}
        scopes:
          - cloud-platform

At launch, GCE provisions the VM in the specified zone with the chosen machine type. The bootDisk defines the OS image, here pulling from debian-cloud’s public image family. The networkInterfaces property attaches the instance to the default VPC and assigns an ephemeral external IP through accessConfigs. The serviceAccount property grants the VM permissions based on the specified scopes (cloud-platform provides full API access). The metadataStartupScript runs on first boot, executing the provided shell commands.

Enable confidential computing with SEV encryption

Workloads handling sensitive data can use Confidential VM to encrypt memory contents through AMD SEV (Secure Encrypted Virtualization). This protects data while it’s being processed, not just at rest or in transit.

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

const _default = new gcp.serviceaccount.Account("default", {
    accountId: "my-custom-sa",
    displayName: "Custom SA for VM Instance",
});
const confidentialInstance = new gcp.compute.Instance("confidential_instance", {
    networkInterfaces: [{
        accessConfigs: [{}],
        network: "default",
    }],
    name: "my-confidential-instance",
    zone: "us-central1-a",
    machineType: "n2d-standard-2",
    minCpuPlatform: "AMD Milan",
    confidentialInstanceConfig: {
        enableConfidentialCompute: true,
        confidentialInstanceType: "SEV",
    },
    bootDisk: {
        initializeParams: {
            image: "ubuntu-os-cloud/ubuntu-2204-lts",
            labels: {
                my_label: "value",
            },
        },
    },
    scratchDisks: [{
        "interface": "NVME",
    }],
    serviceAccount: {
        email: _default.email,
        scopes: ["cloud-platform"],
    },
});
import pulumi
import pulumi_gcp as gcp

default = gcp.serviceaccount.Account("default",
    account_id="my-custom-sa",
    display_name="Custom SA for VM Instance")
confidential_instance = gcp.compute.Instance("confidential_instance",
    network_interfaces=[{
        "access_configs": [{}],
        "network": "default",
    }],
    name="my-confidential-instance",
    zone="us-central1-a",
    machine_type="n2d-standard-2",
    min_cpu_platform="AMD Milan",
    confidential_instance_config={
        "enable_confidential_compute": True,
        "confidential_instance_type": "SEV",
    },
    boot_disk={
        "initialize_params": {
            "image": "ubuntu-os-cloud/ubuntu-2204-lts",
            "labels": {
                "my_label": "value",
            },
        },
    },
    scratch_disks=[{
        "interface": "NVME",
    }],
    service_account={
        "email": default.email,
        "scopes": ["cloud-platform"],
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_default, err := serviceaccount.NewAccount(ctx, "default", &serviceaccount.AccountArgs{
			AccountId:   pulumi.String("my-custom-sa"),
			DisplayName: pulumi.String("Custom SA for VM Instance"),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewInstance(ctx, "confidential_instance", &compute.InstanceArgs{
			NetworkInterfaces: compute.InstanceNetworkInterfaceArray{
				&compute.InstanceNetworkInterfaceArgs{
					AccessConfigs: compute.InstanceNetworkInterfaceAccessConfigArray{
						&compute.InstanceNetworkInterfaceAccessConfigArgs{},
					},
					Network: pulumi.String("default"),
				},
			},
			Name:           pulumi.String("my-confidential-instance"),
			Zone:           pulumi.String("us-central1-a"),
			MachineType:    pulumi.String("n2d-standard-2"),
			MinCpuPlatform: pulumi.String("AMD Milan"),
			ConfidentialInstanceConfig: &compute.InstanceConfidentialInstanceConfigArgs{
				EnableConfidentialCompute: pulumi.Bool(true),
				ConfidentialInstanceType:  pulumi.String("SEV"),
			},
			BootDisk: &compute.InstanceBootDiskArgs{
				InitializeParams: &compute.InstanceBootDiskInitializeParamsArgs{
					Image: pulumi.String("ubuntu-os-cloud/ubuntu-2204-lts"),
					Labels: pulumi.StringMap{
						"my_label": pulumi.String("value"),
					},
				},
			},
			ScratchDisks: compute.InstanceScratchDiskArray{
				&compute.InstanceScratchDiskArgs{
					Interface: pulumi.String("NVME"),
				},
			},
			ServiceAccount: &compute.InstanceServiceAccountArgs{
				Email: _default.Email,
				Scopes: pulumi.StringArray{
					pulumi.String("cloud-platform"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var @default = new Gcp.ServiceAccount.Account("default", new()
    {
        AccountId = "my-custom-sa",
        DisplayName = "Custom SA for VM Instance",
    });

    var confidentialInstance = new Gcp.Compute.Instance("confidential_instance", new()
    {
        NetworkInterfaces = new[]
        {
            new Gcp.Compute.Inputs.InstanceNetworkInterfaceArgs
            {
                AccessConfigs = new[]
                {
                    null,
                },
                Network = "default",
            },
        },
        Name = "my-confidential-instance",
        Zone = "us-central1-a",
        MachineType = "n2d-standard-2",
        MinCpuPlatform = "AMD Milan",
        ConfidentialInstanceConfig = new Gcp.Compute.Inputs.InstanceConfidentialInstanceConfigArgs
        {
            EnableConfidentialCompute = true,
            ConfidentialInstanceType = "SEV",
        },
        BootDisk = new Gcp.Compute.Inputs.InstanceBootDiskArgs
        {
            InitializeParams = new Gcp.Compute.Inputs.InstanceBootDiskInitializeParamsArgs
            {
                Image = "ubuntu-os-cloud/ubuntu-2204-lts",
                Labels = 
                {
                    { "my_label", "value" },
                },
            },
        },
        ScratchDisks = new[]
        {
            new Gcp.Compute.Inputs.InstanceScratchDiskArgs
            {
                Interface = "NVME",
            },
        },
        ServiceAccount = new Gcp.Compute.Inputs.InstanceServiceAccountArgs
        {
            Email = @default.Email,
            Scopes = new[]
            {
                "cloud-platform",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.compute.Instance;
import com.pulumi.gcp.compute.InstanceArgs;
import com.pulumi.gcp.compute.inputs.InstanceNetworkInterfaceArgs;
import com.pulumi.gcp.compute.inputs.InstanceConfidentialInstanceConfigArgs;
import com.pulumi.gcp.compute.inputs.InstanceBootDiskArgs;
import com.pulumi.gcp.compute.inputs.InstanceBootDiskInitializeParamsArgs;
import com.pulumi.gcp.compute.inputs.InstanceScratchDiskArgs;
import com.pulumi.gcp.compute.inputs.InstanceServiceAccountArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var default_ = new Account("default", AccountArgs.builder()
            .accountId("my-custom-sa")
            .displayName("Custom SA for VM Instance")
            .build());

        var confidentialInstance = new Instance("confidentialInstance", InstanceArgs.builder()
            .networkInterfaces(InstanceNetworkInterfaceArgs.builder()
                .accessConfigs(InstanceNetworkInterfaceAccessConfigArgs.builder()
                    .build())
                .network("default")
                .build())
            .name("my-confidential-instance")
            .zone("us-central1-a")
            .machineType("n2d-standard-2")
            .minCpuPlatform("AMD Milan")
            .confidentialInstanceConfig(InstanceConfidentialInstanceConfigArgs.builder()
                .enableConfidentialCompute(true)
                .confidentialInstanceType("SEV")
                .build())
            .bootDisk(InstanceBootDiskArgs.builder()
                .initializeParams(InstanceBootDiskInitializeParamsArgs.builder()
                    .image("ubuntu-os-cloud/ubuntu-2204-lts")
                    .labels(Map.of("my_label", "value"))
                    .build())
                .build())
            .scratchDisks(InstanceScratchDiskArgs.builder()
                .interface_("NVME")
                .build())
            .serviceAccount(InstanceServiceAccountArgs.builder()
                .email(default_.email())
                .scopes("cloud-platform")
                .build())
            .build());

    }
}
resources:
  default:
    type: gcp:serviceaccount:Account
    properties:
      accountId: my-custom-sa
      displayName: Custom SA for VM Instance
  confidentialInstance:
    type: gcp:compute:Instance
    name: confidential_instance
    properties:
      networkInterfaces:
        - accessConfigs:
            - {}
          network: default
      name: my-confidential-instance
      zone: us-central1-a
      machineType: n2d-standard-2
      minCpuPlatform: AMD Milan
      confidentialInstanceConfig:
        enableConfidentialCompute: true
        confidentialInstanceType: SEV
      bootDisk:
        initializeParams:
          image: ubuntu-os-cloud/ubuntu-2204-lts
          labels:
            my_label: value
      scratchDisks:
        - interface: NVME
      serviceAccount:
        email: ${default.email}
        scopes:
          - cloud-platform

Confidential computing requires specific hardware. The machineType must be an N2D series instance (AMD EPYC processors), and minCpuPlatform must specify “AMD Milan” or newer. The confidentialInstanceConfig enables memory encryption by setting enableConfidentialCompute to true and specifying the encryption type (SEV). This configuration ensures data remains encrypted in memory even from the hypervisor.

Beyond These Examples

These snippets focus on specific instance-level features: boot disk configuration and image selection, service account attachment and IAM scopes, startup scripts for bootstrapping, and confidential computing with memory encryption. They’re intentionally minimal rather than full VM deployments.

The examples rely on pre-existing infrastructure such as service accounts with appropriate IAM permissions and the default VPC network (or custom VPC if needed). They focus on configuring the instance rather than provisioning the surrounding networking and IAM.

To keep things focused, common instance patterns are omitted, including:

  • Shielded VM configuration (shieldedInstanceConfig)
  • GPU accelerators (guestAccelerators)
  • Custom VPC and subnet configuration
  • Additional disk attachment (attachedDisks)
  • Network tags and firewall integration
  • Instance resizing controls (allowStoppingForUpdate)

These omissions are intentional: the goal is to illustrate how each instance feature is wired, not provide drop-in VM modules. See the Compute Instance resource reference for all available configuration options.

Frequently Asked Questions

Instance Updates & Lifecycle
Why is my instance update failing?
Many properties require stopping the instance to update. Set allowStoppingForUpdate to true or manually set desiredStatus to TERMINATED before updating machineType, minCpuPlatform, serviceAccount, shieldedInstanceConfig, or enableDisplay.
Why can't I delete my instance?
Instances with deletionProtection enabled cannot be deleted. Disable deletionProtection before running pulumi destroy.
What properties are immutable after creation?
You cannot change name, zone, bootDisk, networkInterfaces, confidentialInstanceConfig, guestAccelerators, project, reservationAffinity, scratchDisks, hostname, instanceEncryptionKey, keyRevocationActionType, networkPerformanceConfig, or params after creation. Changing these forces a new instance.
Machine Types & Compute Resources
How do I resize my VM?
Update machineType, but you must set allowStoppingForUpdate to true for the change to succeed.
Why am I seeing perpetual diffs with custom machine types?
API limitations can convert custom machine types to equivalent standard types, causing non-empty plans. Use lifecycle.ignore_changes on machineType to suppress these diffs.
How do I create a custom machine type with more than 6.5GB per CPU?
Add the -ext suffix for extended memory, such as custom-2-15360-ext for 2 vCPUs and 15GB of memory.
GPUs & Accelerators
Why aren't my GPU accelerators working?
GPU accelerators require scheduling.onHostMaintenance set to TERMINATE. They won’t function with other maintenance options.
Security & Confidential Computing
How do I enable Confidential Computing?
Use an N2D machine type (e.g., n2d-standard-2), set minCpuPlatform to AMD Milan, and configure confidentialInstanceConfig with enableConfidentialCompute: true and confidentialInstanceType: 'SEV'.
Why isn't my Shielded Instance Config working?
shieldedInstanceConfig only works with boot images that have shielded VM support. Verify your image is on the compatible list.
Metadata & Startup Scripts
What's the difference between metadata.startup-script and metadataStartupScript?
metadata.startup-script can be updated without recreating the instance, while metadataStartupScript forces recreation on any change. The two mechanisms cannot be used simultaneously.
Why does metadataStartupScript cause a destroy/recreate after import?
On import, metadataStartupScript isn’t set in state, causing an immediate diff. You’ll need to modify your state file if you want to specify this property after importing.
Networking & Performance
Why isn't networkPerformanceConfig taking effect?
networkPerformanceConfig requires three conditions: a supported machineType, an image with GVNIC in guest-os-features, and network_interface.0.nic-type set to GVNIC. Verify all three are configured correctly.

Ready to get started?

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Create free account