How to create, version, share, and maintain custom images on Google Compute Engine for repeatable, production-grade VM deployments.
What Are Custom Images?
A custom image is a bootable copy of a disk that you own. It captures the OS, installed software, configuration files, and file system layout at a specific point in time. When you create a VM from a custom image, Compute Engine stamps that disk contents onto a fresh Persistent Disk and boots from it. After that, the VM and the image are independent.
Custom images are a global resource. They are not tied to a specific zone or region, though you can control where they are stored.
In practice: If you find yourself writing long startup scripts that install the same packages on every VM, you should probably build a custom image instead. Pre-bake the software into the image and skip the install step at boot time.
Custom Image vs Startup Script vs Machine Image
| Aspect | Custom Image | Startup Script | Machine Image |
|---|---|---|---|
| What it captures | Single disk contents | Commands to run at boot | All disks + full VM configuration |
| Boot speed | Fast (everything pre-installed) | Slow (installs on every boot) | Fast |
| VM config included? | No (disk only) | No | Yes (machine type, network, labels, metadata) |
| Failure risk | Low (no runtime dependencies) | High (network issues, package repo outages can break it) | Low |
| Cost | Storage cost varies by location; common regions are about $0.050/GiB/month as of May 2026 | Free | Storage cost varies by location; common regions are about $0.050/GiB/month as of May 2026 |
| Best for | Fleet deployments, autoscaling, golden images | Simple setups, dev/test, one-off configuration | VM cloning, backup/restore, migration |
Pick a custom image when: You need many identical VMs that boot fast and start in a known state. This is the standard for production fleets.
Pick a startup script when: You are prototyping, running a single VM, or the setup is simple enough that boot-time installation is fine.
Pick a machine image when: You need to clone or back up an entire VM including its configuration and all attached disks.
When Custom Images Are Worth It
Custom images are standard practice in production environments where you run fleets of VMs. Here is when they make a real difference:
Autoscaling and Managed Instance Groups: When a MIG scales out, new VMs need to be ready fast. A custom image boots in seconds with everything pre-installed. A startup script that downloads and installs packages can add minutes to every scale-out event.
Security hardening: Organizations bake CIS benchmarks, endpoint security agents, audit logging daemons, and compliance configurations into an image. Every VM starts in a known-secure state rather than relying on a script to apply hardening correctly at boot.
Compliance requirements: Regulated industries (finance, healthcare, government) need auditable, repeatable infrastructure. A versioned custom image is a concrete artifact you can point to during an audit.
Complex software stacks: When your VMs need specific runtimes, database client libraries, proprietary tools, or application code baked in, a custom image avoids the complexity and fragility of installing everything at boot.
When you do not need them: Dev and test environments, quick prototypes, single VMs, or cases where a short startup script does the job. Custom images add operational overhead (build pipeline, versioning, patching) that is not worth it for small setups.
Creating a Custom Image
The Standard Workflow
- Launch a VM from a public base image
- SSH in and install your software, apply configurations
- Stop the VM (recommended for clean disk state)
- Create the image from the boot disk
# Step 1: Create a base VM
gcloud compute instances create image-builder \
--machine-type=e2-medium \
--image-family=debian-12 \
--image-project=debian-cloud \
--zone=us-central1-a
# Step 2: SSH in and install software
gcloud compute ssh image-builder --zone=us-central1-a
# ... install packages, configure, etc ...
# Step 3: Stop the VM for a clean image
gcloud compute instances stop image-builder --zone=us-central1-a
# Step 4: Create the image
gcloud compute images create my-app-v1 \
--source-disk=image-builder \
--source-disk-zone=us-central1-a \
--family=my-app \
--description="Base image with nginx and app v1"Warning: You can use
--forceto create an image from a running VM, but this risks capturing an inconsistent disk state (partial writes, buffered data not flushed). Stop the VM first for reliable images. If you must image a running VM, at minimum flush buffers (sudo sync) and pause applications that are writing to disk.
Other Creation Methods
From a snapshot:
gcloud compute images create my-app-from-snapshot \
--source-snapshot=my-disk-snapshotFrom another image (copy across projects):
gcloud compute images create my-app-copy \
--source-image=my-app-v1 \
--source-image-project=platform-imagesFrom a RAW image in Cloud Storage (for on-prem migrations):
gcloud compute images create imported-image \
--source-uri=gs://my-bucket/disk.raw.tar.gzStorage Location
By default, Compute Engine stores the image in the multi-region closest to the source. You can specify a location for compliance:
gcloud compute images create my-app-v1 \
--source-disk=image-builder \
--source-disk-zone=us-central1-a \
--storage-location=euUse specific regions or multi-regions (us, eu, asia) based on where your VMs run and any data residency requirements.
Rate Limits
You can create one image per disk every 10 minutes, with a burst of up to 6 requests per 60 minutes. Plan your build pipeline around these limits.
Image Families
An image family is a named group of related images. The family always resolves to the most recent non-deprecated image in that group. Think of it as a latest pointer that automatically updates.
# Create an image and assign it to a family
gcloud compute images create my-app-v2 \
--source-disk=image-builder \
--source-disk-zone=us-central1-a \
--family=my-app
# Create a VM using the family (always gets the latest version)
gcloud compute instances create web-server \
--image-family=my-app \
--image-project=my-project \
--zone=us-central1-aKey Point: Use image families when you want automation to follow the latest approved image in a release channel. Pin a specific image name when you need deterministic rollouts, exact rollback targets, or audit evidence that a VM came from one immutable image version.
Deprecation and Lifecycle
Compute Engine supports deprecation states to manage the lifecycle of custom images:
| State | Effect |
|---|---|
| ACTIVE | Default. Image is fully usable. |
| DEPRECATED | Still usable, but the image family no longer resolves to it. A replacement image can be specified. |
| OBSOLETE | Cannot be used to create new VMs. Use this when you want to block new launches before deleting the image. |
| DELETED | Marked for deletion. You cannot create new VMs from it. |
Deprecate an image and point to its replacement:
gcloud compute images deprecate my-app-v1 \
--state=DEPRECATED \
--replacement=my-app-v2Delete an old image (only after confirming nothing references it):
gcloud compute images delete my-app-v1Tip: Deprecate old images but do not delete them immediately. Keep them around in case you need to roll back. Only delete after confirming no automation, templates, or running VMs still reference the old image.
Sharing Images Across Projects
The standard enterprise pattern is a dedicated image project — a single GCP project that stores approved golden images. Application teams in other projects reference images from that project.
Note: The examples below write service account addresses with
[at]instead of the normal email separator to avoid site rendering issues. Use the normal separator when running the command.
Grant access to a specific image:
gcloud compute images add-iam-policy-binding my-app-v2 \
--project=platform-images \
--member='serviceAccount:123456-compute[at]developer.gserviceaccount.com' \
--role=roles/compute.imageUserOr grant project-level access (all images in the project):
gcloud projects add-iam-policy-binding platform-images \
--member='serviceAccount:123456-compute[at]developer.gserviceaccount.com' \
--role=roles/compute.imageUserConsume the shared image:
gcloud compute instances create web-server \
--image-family=my-app \
--image-project=platform-images \
--zone=us-central1-aThere are no network transfer fees for creating images or for creating disks from images, regardless of where the image is stored.
Golden Image Pipeline
In production, you typically automate the image build process rather than manually creating VMs and running commands. The workflow looks like this:
Build VM from public image
→ Install software and apply configuration
→ Generalize (remove instance-specific data, secrets, SSH keys)
→ Create image and assign to family
→ Test (launch a VM, verify it works)
→ Deprecate previous version
→ Update instance templates to use the new image familyCommon automation tools:
| Tool | How It Fits |
|---|---|
| Packer (HashiCorp) | Build images declaratively. Define what to install in a template file, Packer creates the VM, runs your provisioners, and creates the image. Most popular option. |
| Cloud Build | GCP-native CI/CD. Trigger a build pipeline on code change that produces a new custom image. |
| Terraform | Can create images, but Packer is better suited for the build step. Terraform is used to consume images (instance templates, VMs). |
Versioning strategy: Use a consistent naming convention. Examples: my-app-v1, my-app-v2, or date-based my-app-20260518. Treat the image family as the moving channel and the image name as the immutable version. Use the family for rolling updates and the exact image name when a deployment must be reproducible.
Best Practices
| Practice | Why |
|---|---|
| Stop VMs before imaging | Avoids inconsistent disk state from partial writes and buffered data |
| Never bake secrets into images | Every VM created from the image inherits its contents. Use Secret Manager or inject secrets at boot |
| Use image families intentionally | Families work well for rolling release channels. Pin exact image names for deterministic rollouts, audits, or controlled rollback. |
| Test every new image | Launch a test VM from the image before updating production templates |
| Rebuild regularly for OS patches | Base images get security updates. Periodically rebuild from the latest public image, re-apply your configuration, and release a new version |
| Deprecate, do not immediately delete | Keep old images for rollback. Delete only after confirming nothing references them |
| Use guest OS features | Enable GVNIC for high network bandwidth, UEFI_COMPATIBLE for Shielded VMs, SEV_SNP_CAPABLE for Confidential VMs |
| Use a dedicated image project | Centralize golden images in one project. Share via IAM. Avoids duplication across teams |
| Automate the build pipeline | Manual image creation does not scale. Use Packer or Cloud Build for repeatable, tested builds |
TL;DR
- Custom images capture a single disk’s contents (OS, software, config). Use them when you need fast, consistent VM deployments at scale.
- Prefer custom images over startup scripts for production fleets. Faster boot, no runtime dependency on package repos, consistent state.
- Assign images to a family for rolling release channels. Pin exact image names when reproducibility matters.
- Manage lifecycle with deprecation states (DEPRECATED → OBSOLETE → DELETED). Keep old images for rollback.
- Share images across projects with
roles/compute.imageUserIAM bindings. Use a dedicated image project for golden images. - Automate builds with Packer or Cloud Build. Manual image creation does not scale past a handful of images.
- As of May 2026, common-region storage is about
$0.050/GiB/month; a 20 GiB image is roughly$1/month. Check the pricing page for your storage location.
Resources
Create Custom Images Official guide for creating images from disks, snapshots, and Cloud Storage.
Image Management Best Practices Google’s recommended patterns for building, versioning, and sharing images.
Image Families Best Practices How to use image families effectively for rolling updates.
Deprecate Custom Images Deprecation states and lifecycle management.
Share Images Across Projects IAM patterns for cross-project image sharing.
Disk and Image Pricing Storage costs for custom images and snapshots.
Machine Types and Images Overview of public images, image families, and the snapshots vs images distinction.
VM Startup Scripts When to use startup scripts instead of custom images.
Instance Templates How templates reference images for managed instance groups.