Building Google Compute Engine images with Packer is surprisingly complex because the "image" isn’t just a disk image; it’s a full-fledged resource with specific metadata, launch permissions, and dependencies that Packer needs to manage in concert with the underlying GCE API.

Let’s see it in action. Imagine we want to build a custom Debian 11 image with nginx pre-installed.

{
  "builders": [
    {
      "type": "googlecompute",
      "project_id": "my-gcp-project-12345",
      "source_image_family": "debian-11",
      "zone": "us-central1-a",

      "image_name": "nginx-debian11-{{timestamp}}",

      "ssh_username": "packer"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "sudo apt-get update",
        "sudo apt-get install -y nginx"
      ]
    }
  ]
}

When you run packer build your-template.json, Packer orchestrates a sequence of GCE API calls. It first provisions a temporary Compute Engine instance using the source_image_family you specified. Then, it connects to this instance via SSH (using the ssh_username and ephemeral credentials) to run your provisioners. Once provisioning is complete, Packer stops the instance, creates a disk from its boot disk, and then registers that disk as a new GCE image resource. This image resource is what you’ll use later when launching new VMs.

The core problem Packer solves here is automating the creation of repeatable, versioned VM images. Instead of manually launching a VM, configuring it, and then creating an image, Packer codifies that entire process. This is crucial for immutable infrastructure patterns, where you replace rather than update servers, ensuring consistency and reducing configuration drift.

Internally, Packer uses the googlecompute builder, which abstracts the GCE API. It handles the lifecycle of the temporary VM: creation, waiting for SSH to be available, running provisioners, stopping, and then creating the image. The image_name is vital for versioning; using {{timestamp}} ensures each build gets a unique name.

The source_image_family is a powerful GCE concept. It’s not a single image but a pointer to the latest image within a family, like debian-11 or ubuntu-2004-lts. Packer leverages this to always start from a known, up-to-date base.

Think about the ssh_username. Packer doesn’t know your SSH keys. Instead, when it starts the temporary instance, it injects SSH keys into the instance’s metadata. These keys are then accessible to the googlecompute builder for the duration of the build, allowing it to connect.

The zone parameter is important because Compute Engine resources are zonal. Packer needs to know where to create the temporary instance and where the resulting image will reside.

One aspect that often trips people up is how Packer handles SSH connectivity. It doesn’t rely on your local ~/.ssh/config or global SSH keys. Instead, it dynamically generates SSH keys and adds them to the instance’s metadata during its creation. It then uses these temporary keys to establish the connection for running your provisioners. This is why you don’t need to pre-configure SSH access to the temporary instance.

After you’ve built your image, the next step is typically to use it in your deployment workflows, perhaps with Terraform or by manually launching instances from the GCP Console or gcloud.

Want structured learning?

Take the full Packer course →