On this page
- Authentication: you have two credentials, not one
- Projects, configurations and defaults
- Enabling APIs: the blocker that only exists here
- IAM: inheritance, and the actAs trap
- Org policy: constraints that beat Owner
- Quotas versus capacity
- GKE
- Etags, preconditions and concurrent modification
- Logging and diagnosis
- Output, filters and scripting
- Troubleshooting specific errors
- Production checklist
- Frequently asked questions
- Related resources
Google Cloud fails in four recognisable ways, and only one of them exists on the other major clouds. The API is not enabled. You are holding the wrong one of two credentials. IAM will not let you act as a service account. An org policy overrules you regardless of your role. Get those four straight and most PERMISSION_DENIED triage becomes mechanical. This reference is organized around them.
Authentication: you have two credentials, not one
This is the single most confusing thing about gcloud, and it explains an enormous share of “works in my terminal, fails in Terraform”.
Authentication & identity
| Command | What it does | Risk |
|---|---|---|
gcloud auth login | Credentials for the gcloud CLI itself. | Safe |
gcloud auth application-default login | Application Default Credentials — what SDKs and Terraform read. | Safe |
gcloud auth list | Every account gcloud knows, with the ACTIVE one starred. | Safe |
gcloud config list | Active account, project, region and zone. Run this first when confused. | Safe |
gcloud auth print-access-token | Prove you can actually mint a token right now. | Caution |
gcloud auth revoke <account> | Drop a stale credential rather than debugging around it. | Caution |
gcloud <cmd> --impersonate-service-account=<sa> | Act as a service account without a key file. Needs TokenCreator. | Caution |
gcloud auth application-default set-quota-project <p> | Fixes the 'quota project not set' warning from SDK calls. | Caution |
No commands match that filter.
Application Default Credentials resolve in this order:
1. GOOGLE_APPLICATION_CREDENTIALS an explicit key file path
2. gcloud ADC file ~/.config/gcloud/application_default_credentials.json
3. attached service account the metadata server on GCE, GKE, Cloud Run, Cloud Functions
Credential failures: missing authentication credential 401, reauthentication required, invalid_grant / JWT signature (often clock skew or a rotated key), insufficient authentication scopes — a VM’s scopes are separate from its IAM roles — and workload identity default credentials.
Projects, configurations and defaults
gcloud config list # active account, project, region, zone
gcloud config set project <project-id>
gcloud projects list --format="table(projectId, name, projectNumber)"
# Named configurations — keep prod and dev cleanly separated
gcloud config configurations create prod
gcloud config configurations activate prod
gcloud config configurations list
Use the project ID, not the display name or the number — they are three different things and only the ID is accepted in most commands. A wrong-project error is far more common than a permissions error, and looks identical at first.
Enabling APIs: the blocker that only exists here
Every Google Cloud API must be explicitly enabled per project before you can call it. This has no real equivalent on AWS or Azure, and it is the first wall almost everyone hits.
# Is it on?
gcloud services list --enabled --filter="config.name:compute"
# Turn it on (takes a minute or two to propagate)
gcloud services enable compute.googleapis.com
# Bootstrap a project properly, in one go
gcloud services enable \
compute.googleapis.com container.googleapis.com iam.googleapis.com \
logging.googleapis.com monitoring.googleapis.com artifactregistry.googleapis.com
The error reads “API [x] has not been used in project [y] before or it is disabled” with status SERVICE_DISABLED, and it is genuinely a configuration step rather than a permissions problem — see API not enabled. Enable APIs during project bootstrap, not during the first deploy, and note that enabling propagates asynchronously: an immediate retry can still fail.
A disabled billing account produces a similar-looking wall — billing account disabled — and blocks far more than you would expect.
IAM: inheritance, and the actAs trap
Google Cloud’s hierarchy is Organization → Folder → Project → resource, and IAM bindings inherit downward and are additive. A role granted at the folder applies to every project inside it.
IAM diagnosis
| Command | What it does | Risk |
|---|---|---|
gcloud projects get-iam-policy <p> --flatten='bindings[].members' --filter='bindings.members:<email>' --format='table(bindings.role)' | Every role a principal holds on the project. The canonical incantation. | Safe |
gcloud projects add-iam-policy-binding <p> --member=<m> --role=<r> | Grant safely — this does a read-modify-write with the etag for you. | Caution |
gcloud policy-troubleshoot iam <resource> --principal-email=<e> --permission=<perm> | Ask IAM WHY a specific permission is allowed or denied. | Safe |
gcloud iam service-accounts get-iam-policy <sa> | Who may impersonate or act as this service account. | Safe |
gcloud iam roles describe <role> | The exact permissions a role contains. | Safe |
gcloud asset search-all-iam-policies --scope=projects/<p> --query='policy:<email>' | Find a principal's bindings across the whole hierarchy. | Safe |
No commands match that filter.
# Grant actAs on ONE service account — narrow, not project-wide
gcloud iam service-accounts add-iam-policy-binding <sa-email> \
--member="user:deployer@example.com" \
--role="roles/iam.serviceAccountUser"
Other IAM failures: permission denied 403, service account does not exist, secret manager access denied, storage objects 403 and Artifact Registry upload denied.
Org policy: constraints that beat Owner
Org policies are not IAM. They are constraints applied at the organization, folder or project level, and they override permission entirely — a Project Owner cannot do something an org policy forbids.
gcloud resource-manager org-policies list --project=<p>
gcloud resource-manager org-policies describe compute.vmExternalIpAccess --project=<p>
Common ones that surprise people: compute.vmExternalIpAccess (no public IPs), iam.disableServiceAccountKeyCreation (no downloadable keys), compute.requireShieldedVm, and residency constraints restricting which regions you may deploy into. The error names the constraint — read it rather than editing the request, because the policy usually exists deliberately. See org policy violation.
Quotas versus capacity
Two different failures that both stop a deployment, with different responses.
Which error means which
| Command | What it does | Risk |
|---|---|---|
RESOURCE_EXHAUSTED / QUOTA_EXCEEDED | A project QUOTA. Request an increase; the capacity exists. | Safe |
ZONE_RESOURCE_POOL_EXHAUSTED | Google has no capacity in that ZONE right now. Try another zone or machine type. | Safe |
RESOURCE_OPERATION_RATE_EXCEEDED | Too many operations on one resource too quickly. Back off. | Safe |
No commands match that filter.
# Project-wide quotas and current usage
gcloud compute project-info describe --project=<p> \
--format="table(quotas.metric, quotas.usage, quotas.limit)"
# Regional quotas — these bite far more often than global ones
gcloud compute regions describe us-central1 \
--format="table(quotas.metric, quotas.usage, quotas.limit)"
Compute quotas are largely regional, so a project with plenty of global headroom still fails in one region. Guides: resource exhausted / quota exceeded, CPUs quota exceeded, zone resource pool exhausted, in-use addresses quota, BigQuery concurrent query quota and resource operation rate exceeded.
GKE
gcloud container clusters get-credentials <cluster> --region <r> --project <p>
gcloud container clusters list --format="table(name, location, status, currentMasterVersion)"
gcloud container node-pools list --cluster=<c> --region=<r>
Workload Identity is the right way for a pod to authenticate: bind the Kubernetes service account to a Google service account with roles/iam.workloadIdentityUser and annotate the KSA. No key file ever enters the cluster. When it is misconfigured the pod falls back to the node’s default credentials and fails confusingly — workload identity default credentials.
GKE scheduling and capacity failures: insufficient CPU / unschedulable, untolerated taint, pod exceeded ResourceQuota, node disk pressure eviction, nodes unhealthy, IP space exhausted and ImagePullBackOff from Artifact Registry.
Etags, preconditions and concurrent modification
Google Cloud uses etags for optimistic concurrency on policies and many resources. Read-modify-write is the required pattern, and doing it by hand is where 412s come from.
# The safe pattern — gcloud handles the etag for you
gcloud projects add-iam-policy-binding <p> --member=<m> --role=<r>
# The manual pattern — only when you must edit several bindings at once
gcloud projects get-iam-policy <p> --format=json > policy.json
# ...edit policy.json, KEEPING the etag field intact...
gcloud projects set-iam-policy <p> policy.json
See precondition failed 412, 409 already exists, resource in use by another resource and Cloud SQL operation already in progress.
Logging and diagnosis
Cloud Logging is queryable directly from the CLI, which is faster than the console for most triage:
# Recent errors from one resource type
gcloud logging read 'resource.type="gce_instance" severity>=ERROR' \
--limit=20 --freshness=1h --format="table(timestamp, resource.labels.instance_id, textPayload)"
# Everything about one Cloud Run revision
gcloud logging read 'resource.type="cloud_run_revision" AND resource.labels.service_name="<svc>"' \
--limit=50 --freshness=30m
# Who changed this? Admin Activity audit logs
gcloud logging read 'logName:"cloudaudit.googleapis.com%2Factivity"' --limit=20 \
--format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.methodName)"
# The raw HTTP exchange, when nothing else explains it
gcloud <command> --log-http
Output, filters and scripting
Making gcloud scriptable
| Command | What it does | Risk |
|---|---|---|
--format='value(name)' | Bare values, one per line — the right choice for shell loops. | Safe |
--format='table(name, status, zone)' | Human-readable columns. | Safe |
--format=json | jq | Full structured output for anything non-trivial. | Safe |
--filter='status=RUNNING AND zone:us-central1' | Narrow the result set. See the caveat below. | Safe |
--flatten='bindings[].members' | Expand a nested list so --filter can match inside it. | Safe |
--quiet | Never prompt. Essential in CI, dangerous interactively. | Caution |
--log-http | Print the underlying HTTP request and response. | Safe |
gcloud components update | Update the CLI and installed components. | Caution |
No commands match that filter.
# A safe scripting shape
set -euo pipefail
gcloud config set core/disable_prompts true # or pass --quiet per command
for name in $(gcloud compute instances list --format='value(name)' --filter='status=RUNNING'); do
gcloud compute instances describe "$name" --format='value(machineType)'
done
Troubleshooting specific errors
The Google Cloud failures engineers hit most often, each with a dedicated guide:
- serviceAccounts.actAs denied — deploying as a service account needs a separate grant.
- API not enabled / SERVICE_DISABLED — enable it per project during bootstrap.
- permission denied 403 — walk the hierarchy, then check org policy.
- org policy violation — a constraint overriding your role.
- precondition failed 412 — an etag changed under a read-modify-write.
- zone resource pool exhausted — capacity, not quota. Try another zone.
- resource exhausted / quota exceeded — a real quota; most compute quotas are regional.
- gke-gcloud-auth-plugin not found — install the plugin, re-run get-credentials.
- insufficient authentication scopes — VM scopes are separate from IAM roles.
- reauthentication required — the session aged out mid-script.
- Cloud Run container failed to start — usually the port contract, not the image.
- load balancer 502 backend unhealthy and 504 — health check path, port, or a slow backend.
- Firestore query requires an index — the error contains the link that creates it.
- SSL certificate failed / not visible — managed certs need DNS resolving first.
For anything else, browse the error library or paste the message into the Incident Assistant.
Production checklist
- Know which credential you are using.
gcloud auth loginandapplication-default loginare separate. gcloud config listfirst when anything is unexpectedly denied — wrong project outranks wrong permission.- Enable APIs at project bootstrap, not during the first deploy. Enabling is asynchronous.
- Grant
roles/iam.serviceAccountUseron the service account, not project-wide, when someone must deploy as it. - Check org policy before adding roles. A constraint beats Owner.
- Impersonate instead of downloading keys; use Workload Identity Federation for CI outside Google Cloud.
- Use
add-iam-policy-binding, not get/set, so the etag is handled for you. - Remember compute quotas are regional. Global headroom proves nothing.
- Install
gke-gcloud-auth-pluginbefore blaming kubectl. - Use the project ID, never the name or number.
--format='value(...)'in scripts, and do not assume--filterreduces API load.
Frequently asked questions
Why does gcloud work but my application or Terraform cannot authenticate?
Because they read different credentials. gcloud auth login authenticates the CLI; client libraries, Terraform and the language SDKs read Application Default Credentials, which come from gcloud auth application-default login (or GOOGLE_APPLICATION_CREDENTIALS, or the attached service account on a Google Cloud VM). Running the first alone leaves gcloud fully working while every SDK call fails. Run gcloud auth application-default login and, if you see a quota-project warning, set it with gcloud auth application-default set-quota-project.
I have Owner and still get PERMISSION_DENIED. Why?
Three possibilities, in the order worth checking. The API may not be enabled on the project, which returns a permission-shaped error but is really a configuration step. An org policy constraint may forbid the action outright — those are not IAM and cannot be out-granted, not even by Owner. Or the operation needs iam.serviceAccounts.actAs on a specific service account, which Owner on the project does not automatically confer in the way people expect. gcloud policy-troubleshoot iam will tell you which.
What is the actAs permission and why do I need it?
Creating a resource that runs as a service account — a VM with an attached SA, a Cloud Run service, a Cloud Function, a Dataflow job — is effectively borrowing that identity, so Google requires explicit permission to do it. That permission is iam.serviceAccounts.actAs, normally granted through roles/iam.serviceAccountUser on the service account itself. Without it, someone with full Compute Admin still cannot launch a VM with a service account attached. Grant it per service account rather than at project level.
Do I need to enable an API before using it?
Yes, per project, and this is the main structural difference from AWS and Azure. The error reads “API [x] has not been used in project [y] before or it is disabled” with status SERVICE_DISABLED. Enable it with gcloud services enable <api>.googleapis.com, and note that enabling propagates asynchronously — an immediate retry can still fail, so wait a minute. Enable everything a project needs during bootstrap so this never appears in a deploy.
What is the difference between a quota error and a zone exhaustion error?
RESOURCE_EXHAUSTED or QUOTA_EXCEEDED means your project is not permitted that many, and a quota increase fixes it. ZONE_RESOURCE_POOL_EXHAUSTED means Google itself has no capacity for that machine type in that zone right now — no quota increase helps, and the fix is another zone, another machine type, or waiting. A detail that catches people out: most Compute Engine quotas are regional, so a project with plenty of global headroom can still fail in one region.
Why did my set-iam-policy fail with 412?
Because the policy changed between your get-iam-policy and your set-iam-policy. Google Cloud uses etags for optimistic concurrency: the etag you read is submitted back, and if it no longer matches, the write is rejected rather than silently overwriting someone else’s change. That is the system protecting you. Re-read the policy, reapply your edit, and submit again — and for single-binding changes use add-iam-policy-binding, which does the whole cycle atomically.
Related resources
- Guide: AWS CLI — the same operational ground on AWS; credentials, IAM evaluation and quotas rhyme closely.
- Guide: Azure CLI — and again on Azure, where the identity model is the dominant failure class.
- Guide: Cloud Security — least privilege and the identity model underneath the IAM section here.
- Guide: Kubernetes Security — where GKE workloads pick up from cluster-side controls.
- Tool: Incident Assistant — paste a Google Cloud error and get an ordered triage plan.
Did this solve your problem?
That looks like it may contain a secret (key, token, password, or connection string). Please remove it — a note with a detected secret can’t be published.
Thanks — that helps. Published notes appear after a quick review.
Continue learning
Related Core Guides that build on this one.
- AWS CLIAn AWS CLI reference for engineers who operate real accounts — the credential resolution chain, IAM policy evaluation, S3 permission quirks, service quotas and throttling, CloudFormation rollback states, waiters and querying.
- Azure CLIAn Azure CLI reference for engineers who operate real subscriptions — sign-in and service principals, RBAC scopes and the management/data-plane split, deployments and what-if, quotas and capacity, throttling, and reading the errors ARM returns.
- Cloud SecurityCloud security for DevOps — the shared responsibility model, IAM and least privilege, secrets, encryption, network segmentation, and supply-chain defense.
- Kubernetes SecurityHarden Kubernetes for production — RBAC, Pod Security Standards, NetworkPolicy, admission control and runtime security, with secure vs. insecure YAML side by side.