# gcloud CLI Cheat Sheet — Compute: GCE & GKE

> **Tool:** Google Cloud CLI (gcloud)
> **Category:** Cloud CLIs
> **Verified against:** Google Cloud SDK 553.0.0, flags verified via `gcloud compute instances create
> --help`, `gcloud compute instance-templates create --help`, `gcloud compute instance-groups managed
> create --help`, `gcloud compute disks create --help`, `gcloud container clusters create --help`, `gcloud
> container node-pools create --help`, `gcloud container clusters upgrade --help`, and `gcloud run deploy
> --help` run locally, plus current Google Cloud docs on GKE Autopilot vs Standard, 2026-08-29
> **Official docs:** https://cloud.google.com/sdk/gcloud/reference/compute and https://cloud.google.com/sdk/gcloud/reference/container

## What it is and where it fits 🎯

Compute Engine (GCE) is GCP's IaaS VM primitive — the equivalent of an EC2 or Azure VM — and GKE (Google
Kubernetes Engine) is GCP's managed Kubernetes offering, notable for having invented much of what "managed
Kubernetes" means (Google originated Kubernetes itself). This page assumes the project/auth basics from page
`01`. Cloud Run — GCP's fully-managed container platform, sitting one layer above raw compute — is covered
at the end, since it's frequently the better default for a stateless HTTP service that doesn't need
Kubernetes at all.

## Creating a VM instance

```bash
gcloud compute instances create my-instance \
  --zone=us-central1-a --machine-type=e2-medium \
  --image-family=debian-12 --image-project=debian-cloud \
  --network=my-network --subnet=my-subnet --tags=web-server \
  --service-account=my-service@my-project-id.iam.gserviceaccount.com \
  --scopes=cloud-platform
```

`--image-family` tracks the latest image in a family automatically (e.g. `debian-12` always resolves to the
newest Debian 12 build) — pin `--image` to an exact image name instead when you need a reproducible,
non-drifting build for something like a golden-image pipeline. `--service-account` + `--scopes=cloud-
platform` attaches an identity the instance authenticates as by default, with IAM (not instance scopes)
doing the actual access control — the modern pattern; `cloud-platform` is the broad scope that defers all
real restriction to IAM roles on the service account itself.

```bash
gcloud compute instances create batch-worker --zone=us-central1-a \
  --machine-type=e2-standard-4 --provisioning-model=SPOT \
  --instance-termination-action=DELETE --max-run-duration=6h
```

`--provisioning-model=SPOT` (the current name for what used to be called "preemptible") requests
significantly cheaper, interruptible capacity — appropriate for batch/CI workers, not for anything that
can't tolerate a sudden termination with roughly 30 seconds' notice.

## Listing and inspecting instances

```bash
gcloud compute instances list
gcloud compute instances list --filter="zone:us-central1-a"
gcloud compute instances describe my-instance --zone=us-central1-a
gcloud compute instances describe my-instance --zone=us-central1-a --format="value(status)"
```

## Starting, stopping, and deleting instances

```bash
gcloud compute instances start my-instance --zone=us-central1-a
gcloud compute instances stop my-instance --zone=us-central1-a
gcloud compute instances suspend my-instance --zone=us-central1-a    # preserves memory to disk, faster resume than stop
gcloud compute instances resume my-instance --zone=us-central1-a
gcloud compute instances delete my-instance --zone=us-central1-a
gcloud compute instances delete my-instance --zone=us-central1-a --keep-disks=boot   # delete VM, keep its boot disk
```

`suspend`/`resume` is GCE's closer analogue to a laptop's sleep — it preserves the VM's in-memory state to
persistent disk and can resume faster than a cold `start`, at the cost of continuing to be billed for the
reserved disk space while suspended (compute billing stops, similar to Azure's `deallocate`).

## SSHing into an instance

```bash
gcloud compute ssh my-instance --zone=us-central1-a
gcloud compute ssh my-instance --zone=us-central1-a --tunnel-through-iap   # no external IP/firewall rule needed
```

`gcloud compute ssh` handles SSH key generation and propagation to the instance metadata automatically —
`--tunnel-through-iap` is the standard pattern for reaching instances with no public IP, tunneling through
Identity-Aware Proxy instead of opening an SSH-facing firewall rule.

## Managed disks and snapshots

```bash
gcloud compute disks create my-data-disk --zone=us-central1-a --size=128GB --type=pd-ssd
gcloud compute instances attach-disk my-instance --zone=us-central1-a --disk=my-data-disk
gcloud compute disks list --filter="-users:*"                          # unattached (orphaned) disks

gcloud compute disks snapshot my-data-disk --zone=us-central1-a --snapshot-names=my-data-disk-snap
gcloud compute disks create restored-disk --source-snapshot=my-data-disk-snap --zone=us-central1-a
```

`gcloud compute disks list --filter="-users:*"` finds disks with no attached instance — the GCP equivalent
of Azure's `diskState=='Unattached'` check, worth running periodically since a detached persistent disk
keeps billing until explicitly deleted, the same silent-cost trap covered on the Azure compute page.

## Instance templates and Managed Instance Groups (MIGs)

```bash
gcloud compute instance-templates create my-template \
  --machine-type=e2-medium --image-family=debian-12 --image-project=debian-cloud \
  --network=my-network --subnet=my-subnet

gcloud compute instance-groups managed create my-mig \
  --template=my-template --size=3 --zone=us-central1-a

gcloud compute instance-groups managed set-autoscaling my-mig \
  --zone=us-central1-a --min-num-replicas=2 --max-num-replicas=10 --target-cpu-utilization=0.6

gcloud compute instance-groups managed rolling-action start-update my-mig \
  --zone=us-central1-a --version=template=my-new-template   # rolling replace onto a new template
```

A Managed Instance Group is GCP's autoscaling VM group primitive — the equivalent of an Azure VMSS or an AWS
Auto Scaling Group. Instance templates are immutable once created (you can't edit one in place); the rolling
update above is how you deploy a change — create a new template, then roll the MIG onto it, rather than
mutating instances directly.

## Creating a GKE cluster

```bash
gcloud container clusters create my-cluster \
  --zone=us-central1-a --num-nodes=3 --machine-type=e2-medium \
  --release-channel=regular --enable-ip-alias --workload-pool=my-project-id.svc.id.goog
```

- **`--release-channel`** (`rapid`, `regular`, `stable`) hands Kubernetes version management to Google on a
  schedule rather than you pinning an exact version — `regular` is the common production default, balancing
  currency against stability.
- **`--enable-ip-alias`** creates a VPC-native cluster, giving pods real routable IPs from a secondary
  subnet range instead of an overlay network — the current recommended default for new clusters, required
  for several GKE networking features.
- **`--workload-pool`** enables **GKE Workload Identity** — the same pattern this site's Azure page `02`
  describes for AKS: a Kubernetes service account federates to a GCP IAM service account, so pods get real
  GCP credentials with no key file, ever. This has replaced the older node-scoped service-account pattern
  (every pod on a node sharing the node's broad identity) as the recommended default.

## Autopilot vs. Standard mode

```bash
gcloud container clusters create-auto my-autopilot-cluster --region=us-central1
```

```mermaid
quadrantChart
    title GKE mode tradeoff
    x-axis Less operational control --> More operational control
    y-axis Lower baseline overhead --> Higher baseline overhead
    quadrant-1 Standard tuned hard
    quadrant-2 Standard default config
    quadrant-3 Autopilot
    quadrant-4 Rarely the right call
    "Autopilot":::ok: [0.2, 0.25]
    "Standard hands-off":::warn: [0.5, 0.55]
    "Standard tuned":::info: [0.85, 0.7]

    classDef ok color: #1f8a4c, stroke-color: #1f8a4c
    classDef warn color: #b8650f, stroke-color: #b8650f
    classDef info color: #1d6fb8, stroke-color: #1d6fb8
```

Autopilot manages node provisioning, sizing, and most security hardening for you and bills per-pod
(CPU/memory/storage requested), rather than per-node; Standard gives full control over node pools, machine
types, and node-level customization but requires you to actually manage capacity. **You cannot convert an
existing cluster between modes** — the decision has to be made at creation time, and switching later means
standing up a new cluster and migrating workloads. Reach for Standard specifically when you need GPUs beyond
what Autopilot supports, DaemonSets, privileged containers, or direct node SSH access; default to Autopilot
otherwise, per Google's own current guidance.

## Getting kubectl credentials for a cluster

```bash
gcloud container clusters get-credentials my-cluster --zone=us-central1-a
```

Same role as `az aks get-credentials` / `aws eks update-kubeconfig` — writes/merges a kubeconfig entry so
`kubectl` can authenticate to the cluster; it doesn't create or modify anything in the cluster itself.

## Listing clusters and node pools

```bash
gcloud container clusters list
gcloud container node-pools list --cluster=my-cluster --zone=us-central1-a
gcloud container clusters describe my-cluster --zone=us-central1-a --format="value(currentMasterVersion)"
```

## Resizing a cluster's node pool

```bash
gcloud container clusters resize my-cluster --zone=us-central1-a --num-nodes=5
gcloud container clusters resize my-cluster --zone=us-central1-a --node-pool=default-pool --num-nodes=5
```

Manually resizing a node pool is a one-time operation, not a standing policy — if the cluster has a Cluster
Autoscaler configured, a manual resize can be immediately reverted by the autoscaler unless you also adjust
its min/max node bounds.

## Creating and autoscaling node pools

```bash
gcloud container node-pools create high-mem-pool \
  --cluster=my-cluster --zone=us-central1-a \
  --machine-type=e2-highmem-4 --node-locations=us-central1-a,us-central1-b \
  --num-nodes=1 --enable-autoscaling --min-nodes=1 --max-nodes=5

gcloud container node-pools create spot-pool \
  --cluster=my-cluster --zone=us-central1-a \
  --spot --machine-type=e2-standard-4 \
  --node-taints=cloud.google.com/gke-spot=true:NoSchedule \
  --enable-autoscaling --min-nodes=0 --max-nodes=20

gcloud container clusters update my-cluster --zone=us-central1-a \
  --node-pool=default-pool --enable-autoscaling --min-nodes=1 --max-nodes=10

gcloud container clusters update my-cluster --zone=us-central1-a \
  --node-pool=default-pool --no-enable-autoscaling   # turn autoscaling back off
```

`--node-locations` on a node pool spreads its nodes across multiple zones within the cluster's region —
useful for zonal-failure resilience even on a "zonal" cluster. Autoscaling is a per-node-pool setting, not
per-cluster: `clusters update` needs `--node-pool` to target the pool you're actually changing bounds on.
`--spot` combined with `--node-taints` is GKE's version of the Spot-plus-taint pattern this site's Azure
page `02` shows for AKS — keep interruptible capacity dedicated to workloads that explicitly tolerate it via
a matching toleration, `--min-nodes=0` lets the pool cost nothing when idle.

## Upgrading clusters and node pools

```bash
gcloud container get-server-config --zone=us-central1-a --format="value(validMasterVersions)"
gcloud container clusters upgrade my-cluster --zone=us-central1-a --master --cluster-version=1.31.1
gcloud container clusters upgrade my-cluster --zone=us-central1-a --node-pool=default-pool
```

`--master` upgrades the control plane only; omitting it (with `--node-pool` specified) upgrades that node
pool instead — the two are separate operations by design, the same control-plane-vs-node-pool split covered
for AKS on the Azure compute page, letting you validate a new control-plane version before touching worker
nodes.

## Real-world scenario: rolling out a change to a MIG without downtime

A fleet of stateless API workers behind a load balancer needs a new instance template rolled out gradually,
not all at once:

```bash
gcloud compute instance-templates create api-template-v2 --machine-type=e2-standard-2 \
  --image-family=debian-12 --image-project=debian-cloud

gcloud compute instance-groups managed rolling-action start-update my-mig \
  --zone=us-central1-a --version=template=api-template-v2 \
  --max-surge=2 --max-unavailable=0
```

`--max-unavailable=0` combined with `--max-surge=2` keeps full serving capacity throughout the rollout — new
instances on the new template come up *before* old ones are torn down, rather than replacing capacity in
place, avoiding the brief dip in available backends a naive in-place rolling update would cause.

## Real-world scenario: private GKE nodes reachable only through IAP

A security review flagged that every GKE node had a public IP reachable for SSH debugging — closing that
down while keeping operator access:

```bash
gcloud container clusters create my-private-cluster \
  --zone=us-central1-a --enable-private-nodes \
  --master-ipv4-cidr=172.16.0.0/28 --enable-ip-alias

gcloud compute ssh <node-name> --zone=us-central1-a --tunnel-through-iap
```

`--enable-private-nodes` removes public IPs from worker nodes entirely; IAP tunneling (already the standard
SSH pattern from earlier in this page) is what restores operator reachability without reopening a public
attack surface — the access path goes through IAM-authorized IAP, not an open firewall rule.

## Cloud Run — deploying and managing services

```bash
gcloud run deploy my-service \
  --image=us-docker.pkg.dev/my-project/my-repo/my-image:latest \
  --region=us-central1 --allow-unauthenticated \
  --memory=512Mi --cpu=1 --min-instances=0 --max-instances=10 --concurrency=80

gcloud run deploy my-service --source=. --region=us-central1   # build from local source instead of a pre-built image

gcloud run services list --region=us-central1
gcloud run services describe my-service --region=us-central1
gcloud run services update-traffic my-service --region=us-central1 \
  --to-revisions=my-service-00002-abc=10,my-service-00001-xyz=90   # canary: 10% to the new revision
```

`--source=.` hands the build off to Cloud Build automatically (using Buildpacks or a Dockerfile if present)
— no separate `docker build`/`docker push` step needed. `--allow-unauthenticated` makes the service publicly
reachable; omit it (or pass `--no-allow-unauthenticated`) to require IAM-authenticated callers, the default
and the safer starting point for anything not meant to be public.

> [!TIP]
> For a stateless HTTP service with no genuine need for Kubernetes-level control (custom scheduling,
> DaemonSets, a service mesh), default to Cloud Run over standing up a GKE cluster — it scales to zero,
> requires no node/cluster management at all, and the `--source=.` build-from-source path is meaningfully
> less CI/CD plumbing than a container-build-and-`kubectl apply` pipeline.

## CI/CD integration recipe: GitHub Actions deploy to GKE

```yaml
# .github/workflows/deploy-gke.yml
name: Deploy to GKE
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/<project-number>/locations/global/workloadIdentityPools/github-pool/providers/github-provider
          service_account: ci@my-project-id.iam.gserviceaccount.com
      - uses: google-github-actions/get-gke-credentials@v2
        with:
          cluster_name: my-cluster
          location: us-central1-a
      - run: kubectl apply -f k8s/
      - run: kubectl rollout status deployment/my-app --timeout=180s
```

## Common pitfalls

- **Confusing `stop` with `suspend`** — `suspend` preserves memory state and keeps billing reserved disk
  space; `stop` doesn't preserve memory but stops compute billing entirely, closer to a true power-off.
- **Leaving orphaned disks after instance deletion** — check `gcloud compute disks list --filter="-users:*"`
  periodically; a disk isn't deleted automatically unless the instance was created with the disk set to
  auto-delete (the default for a boot disk, not for most attached data disks).
- **Manually resizing an autoscaled node pool/MIG** — the autoscaler can revert it; adjust min/max bounds
  instead.
- **Expecting `gcloud container clusters upgrade` with no `--master`/`--node-pool` distinction to behave
  like a single atomic operation** — control plane and node pool upgrades are separate calls by design.
- **Standing up GKE Standard for a workload that would run fine on Cloud Run or Autopilot** — see the TIP
  above; Kubernetes-level control is a real cost most stateless HTTP services don't need to pay.

## Exit codes

`0` success · non-zero on any API/validation error — a `gcloud container clusters create` that times out
waiting for provisioning still exits non-zero even if the cluster eventually finishes creating in the
background; check `gcloud container operations list` to confirm actual state rather than trusting only the
command's own exit code for a long-running operation.

## When to reach for something else

Use `kubectl`/Helm/GitOps tooling for anything happening *inside* a cluster once it exists — `gcloud
container` commands provision and manage the cluster and node pool shape, not workloads running on it. For
declarative, reviewable provisioning across environments, prefer Terraform's `google_container_cluster`
resource over a growing shell script of `gcloud container`/`gcloud compute` calls, consistent with page
`01`'s IaC guidance.
