# gcloud CLI Cheat Sheet — Configuration, Auth & IAM

> **Tool:** Google Cloud CLI (gcloud)
> **Category:** Cloud CLIs
> **Verified against:** Google Cloud SDK 553.0.0, flags verified via `gcloud auth login --help`, `gcloud iam
> workload-identity-pools --help`, `gcloud iam workload-identity-pools providers create-oidc --help`,
> `gcloud iam service-accounts create --help`, and `gcloud projects add-iam-policy-binding --help` run
> locally, plus `google-github-actions/auth` official docs (workload identity federation for GitHub
> Actions), 2026-08-29
> **Official docs:** https://cloud.google.com/sdk/gcloud/reference

## What it is and where it fits 🎯

`gcloud` is Google Cloud's official command-line tool — the scriptable equivalent of everything the Google
Cloud Console can do, and the same role `aws` and `az` play for their respective clouds. This page covers
authentication, project/configuration switching, and IAM: service accounts, role bindings, and — the
current recommended pattern for CI/CD — Workload Identity Federation. The companion pages cover Compute
Engine/GKE (`02`) and Cloud Storage/networking (`03`).

GCP's identity model has a distinctive shape worth internalizing early: almost everything is scoped to a
**project** (GCP's rough equivalent of an AWS account or an Azure resource group, though with its own
independent billing and IAM), projects nest under **folders**, and folders nest under an **organization**.
A **service account** is itself a first-class IAM identity with its own email-shaped identifier
(`name@project-id.iam.gserviceaccount.com`) — closer to an IAM role in AWS than to an Azure managed
identity, and it's the identity workloads and automation authenticate as.

## Installation

```bash
# Debian/Ubuntu — official apt repo
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
  | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
sudo apt-get update && sudo apt-get install google-cloud-cli

# macOS
brew install --cask google-cloud-sdk

# Any platform — interactive installer
curl https://sdk.cloud.google.com | bash

gcloud version                # confirm install + list installed components
gcloud components update      # upgrade gcloud and every installed component together
gcloud init                   # interactive first-time setup: login, project, default region/zone
```

## Core concepts: the resource + identity hierarchy

```mermaid
flowchart TD
    Org["Organization"] --> Folder["Folder (optional)"]
    Folder --> Proj1["Project: my-app-prod"]
    Folder --> Proj2["Project: my-app-staging"]
    Proj1 --> SA["Service account:<br/>ci@my-app-prod.iam..."]
    Proj1 --> Bucket["Resources: buckets,<br/>VMs, GKE clusters"]
    SA -.->|"IAM role binding"| Bucket

    classDef info fill:#e5f0fa,stroke:#1d6fb8,color:#10161c
    classDef accent fill:#f0e9fb,stroke:#6d43c0,color:#10161c
    class Org info
    class Proj1,Proj2 accent
```

IAM policy bindings can be set at any level of this hierarchy — a binding on a folder or organization is
inherited by every project underneath it, which is powerful for org-wide baselines and a common source of
"why does this identity have access I never granted at the project level" when someone forgets to check
higher up the tree.

## Authenticating

```bash
gcloud auth login                                            # interactive browser login for a user account
gcloud auth login --no-launch-browser                        # for headless/remote shells
gcloud auth activate-service-account --key-file=key.json      # authenticate as a service account (key file)
gcloud auth list                                              # show all authenticated accounts + which is active
gcloud auth revoke my-account@example.com

gcloud auth application-default login                        # separate credential set for client libraries/SDKs
gcloud auth application-default print-access-token
```

> [!IMPORTANT]
> **`gcloud auth login` and `gcloud auth application-default login` set up two different, independent
> credential stores.** The first is what `gcloud` CLI commands themselves use; the second — Application
> Default Credentials (ADC) — is what client libraries (the Python/Go/Node SDKs, Terraform's `google`
> provider) look for when running locally. Running only one of the two is the most common "gcloud works but
> my script/Terraform can't authenticate" (or vice versa) confusion for anyone new to GCP.

## Configurations — named sets of gcloud settings

```bash
gcloud config configurations create staging            # a separate named config (project, account, region, etc.)
gcloud config configurations activate staging           # switch to it
gcloud config configurations list
gcloud config configurations describe staging
```

A "configuration" in gcloud bundles project + account + default region/zone together — the equivalent of an
AWS CLI named profile. Switching configurations is the standard way to work across multiple GCP
projects/accounts from one terminal without re-typing `--project` on every command.

## Setting individual config values

```bash
gcloud config set project my-project-id
gcloud config set compute/region us-central1
gcloud config set compute/zone us-central1-a
gcloud config list                                      # show current effective config
gcloud config unset compute/zone
```

## Projects

```bash
gcloud projects list
gcloud projects describe my-project-id
gcloud projects create my-new-project --folder=<folder-id>        # or --organization=<org-id>
gcloud projects add-iam-policy-binding my-project-id \
  --member="user:jane@example.com" --role="roles/viewer"
gcloud projects get-iam-policy my-project-id             # see the full policy, all bindings at once
```

## Service accounts

```bash
gcloud iam service-accounts create my-service --display-name="My Service"
gcloud iam service-accounts list
gcloud iam service-accounts describe my-service@my-project-id.iam.gserviceaccount.com
gcloud iam service-accounts keys create key.json --iam-account=my-service@my-project-id.iam.gserviceaccount.com
gcloud iam service-accounts keys list --iam-account=my-service@my-project-id.iam.gserviceaccount.com
gcloud iam service-accounts disable my-service@my-project-id.iam.gserviceaccount.com   # freeze without deleting
```

A downloaded service-account key file is a long-lived credential — treat it like a password. For workloads
running on GCP compute (GCE/GKE/Cloud Run), prefer attaching the service account directly to the resource
instead of distributing key files, so there's no static secret to leak or rotate. For anything running
*outside* GCP (GitHub Actions, another cloud), prefer Workload Identity Federation over a downloaded key —
see below.

## Impersonating a service account (instead of downloading its key)

```bash
gcloud auth print-access-token --impersonate-service-account=my-service@my-project-id.iam.gserviceaccount.com
gcloud storage ls --impersonate-service-account=my-service@my-project-id.iam.gserviceaccount.com gs://my-bucket
```

Requires the `roles/iam.serviceAccountTokenCreator` role on the target service account, granted to *your*
identity — impersonation lets you act as a service account temporarily using your own already-authenticated
session, without ever generating or holding a key file for it. This is the standard way to test "does this
service account actually have the access it needs" without handing out its credentials.

## Workload Identity Federation: keyless auth for external systems

Workload Identity Federation (WIF) lets an external identity — a GitHub Actions workflow, a workload running
on AWS/Azure, a Kubernetes service account — exchange its own short-lived token for a GCP access token,
without ever storing a service-account key. This is the GCP equivalent of the federated-credential pattern
covered on this site's Azure CLI page `01`.

```mermaid
sequenceDiagram
    participant GH as GitHub Actions job
    participant OIDC as GitHub OIDC provider
    participant Pool as GCP Workload Identity Pool
    participant SA as GCP Service Account
    participant API as Google Cloud APIs

    GH->>OIDC: Request short-lived OIDC token
    OIDC-->>GH: Signed JWT (repo, branch claims)
    GH->>Pool: Exchange JWT via WIF provider
    Pool->>Pool: Validate JWT against attribute condition
    Pool->>SA: Impersonate mapped service account
    SA-->>GH: Short-lived GCP access token
    GH->>API: Authenticated calls

    Note over Pool: No key file anywhere — trust is the<br/>pool's attribute-mapping and condition
```

```bash
# 1. Create a workload identity pool — a container for external identity providers
gcloud iam workload-identity-pools create github-pool \
  --location=global --display-name="GitHub Actions pool"

# 2. Create an OIDC provider inside it, trusting GitHub's OIDC issuer
gcloud iam workload-identity-pools providers create-oidc github-provider \
  --location=global --workload-identity-pool=github-pool \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --attribute-condition="assertion.repository=='my-org/my-repo'"

# 3. Allow the pool (scoped by the condition above) to impersonate a service account
gcloud iam service-accounts add-iam-policy-binding my-service@my-project-id.iam.gserviceaccount.com \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-repo"
```

> [!IMPORTANT]
> **`--attribute-condition` is not optional in practice** — a pool with no condition accepts a valid token
> from *any* GitHub repository in existence, not just yours, since GitHub is a shared OIDC issuer across
> every public and private repo. Scoping the condition to your specific `repository` (and, for anything
> sensitive, the `ref`/branch too) is what keeps the trust relationship actually narrow. This is the GCP
> analogue of the Azure federated-credential `subject` match described on the Azure CLI page — same
> underlying risk if skipped.

## IAM role bindings

```bash
gcloud iam service-accounts add-iam-policy-binding my-service@my-project-id.iam.gserviceaccount.com \
  --member="user:jane@example.com" --role="roles/iam.serviceAccountUser"
gcloud projects get-iam-policy my-project-id             # see the full policy, all bindings at once
gcloud projects remove-iam-policy-binding my-project-id \
  --member="user:jane@example.com" --role="roles/viewer"
```

`add-iam-policy-binding` is a read-modify-write under the hood — on a resource with many bindings, two
concurrent `add-iam-policy-binding` calls can race and one can silently lose. For scripted/CI changes to a
policy with many existing bindings, `get-iam-policy` + edit + `set-iam-policy` with an explicit etag is the
safer pattern:

```bash
gcloud projects get-iam-policy my-project-id --format=json > policy.json
# edit policy.json — add/remove bindings, keep the "etag" field untouched
gcloud projects set-iam-policy my-project-id policy.json
```

`set-iam-policy` fails outright if the `etag` in your submitted policy doesn't match the current server-side
policy's etag — this is the concurrency-safety mechanism `add-iam-policy-binding` skips.

## IAM conditional bindings

```bash
gcloud projects add-iam-policy-binding my-project-id \
  --member="user:jane@example.com" --role="roles/storage.objectViewer" \
  --condition='expression=request.time < timestamp("2027-01-01T00:00:00Z"),title=expires-2026,description=Temporary access, expires end of 2026'

gcloud projects add-iam-policy-binding my-project-id \
  --member="user:jane@example.com" --role="roles/viewer" --condition=None   # explicitly add a binding with no condition
```

A conditional binding only grants the role while the CEL `expression` evaluates true — commonly used for
time-boxed access grants like the one above. `--role` cannot be a basic role (`roles/owner`, `roles/editor`,
`roles/viewer`) when a real condition is attached; `title` and `expression` are required, `description` is
optional. `--condition-from-file` takes the same fields from a JSON/YAML file instead of an inline
key-value string, which is easier to keep readable for a long CEL expression.

## Custom roles

```bash
gcloud iam roles create myCustomViewer --project=my-project-id \
  --title="My Custom Viewer" --permissions=compute.instances.get,compute.instances.list \
  --stage=GA
gcloud iam roles list --project=my-project-id
```

Custom roles are worth reaching for when a built-in predefined role is either too broad (grants dozens of
permissions a job doesn't need) or, less commonly, doesn't quite cover a narrow combination — but start from
`gcloud iam roles describe roles/storage.objectViewer --format="value(includedPermissions)"` on the closest
predefined role and trim, rather than hand-listing permissions from scratch.

## Config file locations

```bash
ls ~/.config/gcloud/                       # active_config, configurations/, legacy_credentials/
cat ~/.config/gcloud/application_default_credentials.json   # ADC file, when set via `auth application-default login`
```

`gcloud config configurations` are stored as plain INI files under `~/.config/gcloud/configurations/` —
useful to know when scripting a container image that needs a config baked in (mount or `COPY` the file
directly) rather than running `gcloud config set` interactively at build time.

## Real-world scenario: keyless GitHub Actions deploy pipeline

Building on the Workload Identity Federation setup above, the actual CI job needs no secret beyond
identifiers:

```yaml
# .github/workflows/deploy.yml
name: Deploy to GCP
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: my-service@my-project-id.iam.gserviceaccount.com
      - uses: google-github-actions/setup-gcloud@v2
      - run: gcloud storage ls gs://my-bucket
```

> [!TIP]
> `actions/checkout` should run **before** `google-github-actions/auth` in the job — running it after (or
> omitting it) has caused later steps to lose authentication in real setups, since checkout can reset
> environment state the auth action relies on. It's a small ordering detail that's easy to get backwards
> when composing a workflow from separate examples.

## Real-world scenario: cross-project service account impersonation for a shared CI identity

A platform team runs one CI service account in a central `tooling` project but needs it to deploy into
several per-team application projects, without creating a duplicate service account (and duplicate WIF
trust setup) in every project:

```bash
gcloud projects add-iam-policy-binding app-team-a-project \
  --member="serviceAccount:ci@tooling-project.iam.gserviceaccount.com" \
  --role="roles/iam.serviceAccountTokenCreator" \
  --condition=None
```

The CI identity authenticates once (via WIF, into the `tooling` project's service account) and then
impersonates a per-project deployer service account as needed — one federated trust relationship instead of
one per application project, with each application project independently controlling exactly what its own
deployer role can do.

## Common pitfalls

- **Confusing `gcloud auth login` with `gcloud auth application-default login`** — see the IMPORTANT note
  above; they're separate credential stores serving different consumers.
- **Creating a workload identity pool provider with no `--attribute-condition`** — trusts every repository
  on the shared OIDC issuer, not just yours.
- **Racing concurrent `add-iam-policy-binding` calls against a busy policy** — use `get-iam-policy` +
  `set-iam-policy` with the etag for anything scripted against a policy with many existing bindings.
- **Downloading a service-account key when impersonation or WIF would do** — a key file is a permanent
  secret to manage; both alternatives avoid ever creating one.

## Exit codes

`0` success · non-zero on any API/auth/permission error — a `PERMISSION_DENIED` from a missing IAM binding
and an `INVALID_ARGUMENT` from a malformed flag both exit non-zero with no further distinction in the exit
code itself; parse the printed error text or add `--verbosity=debug` for the underlying request/response.

## When to reach for something else

For declarative, reviewable IAM and project provisioning, prefer Terraform's `google`/`google-beta`
providers over scripting `gcloud iam`/`gcloud projects` calls directly, the same declarative-IaC preference
as this site's AWS/Azure CLI pages. `gcloud` remains the right tool for interactive debugging, one-off
operational tasks, and CI glue that doesn't warrant a full Terraform apply cycle.
