Create and Configure Compute Engine Instances

The gcp:compute/instance:Instance resource, part of the Pulumi GCP provider, defines a GCE VM instance: its machine type, boot disk, network placement, and security configuration. This guide focuses on three capabilities: boot disk and image selection, service account attachment, and confidential computing with AMD SEV.

Instances run in a VPC network and zone, use service accounts for API access, and boot from disk images. The examples are intentionally small. Combine them with your own VPC configuration, IAM roles, and security policies.

Launch an instance with boot disk and service account

Most deployments create an instance with a boot disk image, machine type, and service account for API access.

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

The machineType and zone determine compute capacity and location. The bootDisk.initializeParams.image specifies the OS image (here, Debian 11). The networkInterfaces array attaches the instance to a network; accessConfigs grants a public IP. The serviceAccount block attaches an identity for API calls, with scopes controlling permissions. The metadataStartupScript runs on boot, useful for initialization tasks.

Enable confidential computing with AMD SEV

Workloads handling sensitive data can encrypt memory contents during processing using Confidential VM.

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

The confidentialInstanceConfig.enableConfidentialCompute activates memory encryption. This requires an N2D machine type (machineType: “n2d-standard-2”) and AMD Milan CPU platform (minCpuPlatform: “AMD Milan”). The confidentialInstanceType specifies the encryption technology; SEV (Secure Encrypted Virtualization) protects against hypervisor-level access to memory.

Beyond these examples

These snippets focus on specific instance-level features: boot disk and image selection, service account attachment, and confidential computing with AMD SEV. They’re intentionally minimal rather than full VM deployments.

The examples rely on pre-existing infrastructure such as the default VPC network and service accounts with appropriate IAM roles. They focus on configuring the instance rather than provisioning the surrounding infrastructure.

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

  • Custom VPC and subnet configuration
  • GPU accelerators (guestAccelerators)
  • Shielded VM integrity monitoring
  • Attached persistent disks and scratch disks
  • Instance scheduling and maintenance policies
  • Custom metadata and SSH key management

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.

Let's create and Configure Compute Engine Instances

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Updates & Lifecycle
Why is my instance update failing?
Updates to machineType, minCpuPlatform, shieldedInstanceConfig, enableDisplay, or serviceAccount require stopping the instance. Set allowStoppingForUpdate to true, or set desiredStatus to TERMINATED before updating these fields.
Why can't I delete my instance?
If deletionProtection is enabled, the instance cannot be deleted and pulumi destroy will not complete successfully. Disable deletion protection before removing the resource.
What properties can't I change after creation?
The following properties are immutable and require recreating the instance: bootDisk, confidentialInstanceConfig, guestAccelerators, name, networkInterfaces, zone, hostname, project, reservationAffinity, scratchDisks, instanceEncryptionKey, keyRevocationActionType, networkPerformanceConfig, and params.
What's the difference between metadata.startup-script and metadataStartupScript?
Both run scripts on instance boot, but metadataStartupScript forces instance recreation when changed, while metadata.startup-script does not. Use metadataStartupScript only if you want the instance recreated when the script changes.
Machine Types & Resources
How do I create a custom machine type?
Use the format custom-NUMBER_OF_CPUS-AMOUNT_OF_MEMORY_MB, such as custom-6-20480 for 6 vCPU and 20GB RAM. For extended memory (over 6.5 GB per CPU), add the -ext suffix, like custom-2-15360-ext for 2 vCPU and 15 GB of memory.
Why does my custom machine type keep changing?
API limitations may cause some custom machine types to be converted to equivalent standard types, resulting in non-empty plans. Use lifecycle.ignore_changes on machineType to suppress these diffs.
Can I use GPUs with live migration?
No. GPU accelerators can only be used with onHostMaintenance set to TERMINATE in the scheduling configuration.
What's required for 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.
Import & State Management
What should I know about importing instances?
Encryption keys (boot_disk.0.disk_encryption_raw and attached_disk.*.disk_encryption_key_raw) cannot be imported automatically and must be manually added to state. The desiredStatus field will not be set on import; if you configure it, expect a diff on the first pulumi up. If you specify metadataStartupScript on import, you’ll see a destroy/recreate diff unless you modify your state file.
Networking & Performance
What's required for high bandwidth network performance?
To use networkPerformanceConfig, your machineType must be a supported type, the boot image must include GVNIC in guest-os-features, and network_interface.0.nic-type must be set to GVNIC. All three requirements must be met.
Security & Configuration
Can I use Shielded VM with any boot image?
No. shieldedInstanceConfig can only be used with boot images that have shielded VM support. Additionally, you must set allowStoppingForUpdate to true or desiredStatus to TERMINATED to update this field.
What happens to my SSH keys from the Cloud Console?
SSH keys attached in the Cloud Console will be removed when you manage metadata via Pulumi. Add them to your configuration to keep them attached to your instance.
How do startup scripts work with metadata?
Most Linux-based images (Debian, CentOS, RHEL, SLES, Container-Optimized OS, Ubuntu) run the content of metadata.startup-script in a shell on every boot. Windows instances require different keys depending on script format and timing.

Using a different cloud?

Explore compute guides for other cloud providers: