Before launching anything on Google Cloud, learn how resources and access are organised. Most early mistakes (a shared project with everyone as Owner, a downloaded service account key in a repository) come from skipping this step.
The resource hierarchy
flowchart TB
ORG[Organization<br/>example.com] --> FP[Folder production]
ORG --> FN[Folder non-production]
ORG --> FS[Folder shared services]
FP --> P1[Project shop-prod]
FN --> P2[Project shop-dev]
FN --> P3[Project shop-test]
FS --> P4[Project logging and monitoring]
P1 --> R1[Resources<br/>VMs buckets databases]
BA[Billing account] -.->|linked to| P1
BA -.-> P2
POL[IAM policies and<br/>organization policies] -.->|inherited downwards| FP
The project is the main unit of billing, API enablement and permissions, similar to an AWS account. Project IDs are globally unique, between 6 and 30 characters, and cannot be reused. A deleted project stays recoverable for 30 days.
| GCP | AWS idea | Note |
|---|---|---|
| Organization, folders | AWS Organizations, OUs | Policies set here are inherited by everything below |
| Project | Account | Boundary for billing, quotas and APIs |
| IAM role bound to a principal on a resource | Policy attached to a principal | GCP attaches the binding to the resource |
| Service account | IAM role / machine identity | An identity for workloads, with its own email |
Region asia-south1 (Mumbai), asia-south2 (Delhi) |
ap-south-1 |
Pick regions near users and data rules |
IAM policies are additive and inherited: a role granted on a folder applies to every project below it, and permissions granted lower cannot be removed by a lower policy. Deny policies and Organization Policy constraints (for example, restricting regions or blocking service account key creation) are the tools for hard limits.
First setup with the gcloud CLI
curl https://sdk.cloud.google.com | bash && exec -l $SHELL
gcloud init
gcloud auth login
gcloud auth application-default login # credentials for local code and Terraform
gcloud projects create demo-ci-2025 --name="Demo CI" --folder=123456789012
gcloud config set project demo-ci-2025
gcloud config set compute/region asia-south1
gcloud services enable compute.googleapis.com storage.googleapis.com iam.googleapis.com
APIs are enabled per project, so a command that fails with “API not enabled” needs gcloud services enable. Use named configurations (gcloud config configurations create work) to switch between projects safely.
Granting access
- Grant roles to groups, not individuals, and at the lowest scope that works (a project or a single bucket rather than the organization).
- Prefer predefined roles (
roles/storage.objectViewer,roles/compute.viewer) over the broad basic roles Owner, Editor and Viewer.
gcloud projects add-iam-policy-binding demo-ci-2025 \
--member="group:devops@example.com" --role="roles/compute.viewer"
gcloud projects get-iam-policy demo-ci-2025 --format=json | jq '.bindings[] | {role, members}'
Use IAM Recommender and the policy troubleshooter to find roles that are never used or permissions that block a request.
Service accounts without downloaded keys
Give each workload its own service account with only the roles it needs. Avoid downloaded JSON keys: they never expire by themselves and tend to leak. Organizations created since May 2024 get a secure-by-default Organization Policy that blocks creating them; for older organizations, enforce iam.disableServiceAccountKeyCreation yourself.
For CI systems outside Google Cloud, use workload identity federation. A GitHub Actions example:
gcloud iam workload-identity-pools create github --location=global --display-name="GitHub"
gcloud iam workload-identity-pools providers create-oidc github-actions \
--location=global --workload-identity-pool=github \
--issuer-uri="https://token.actions.githubusercontent.com" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--attribute-condition="assertion.repository=='acme/shop-api'"
gcloud iam service-accounts add-iam-policy-binding deployer@demo-ci-2025.iam.gserviceaccount.com \
--role=roles/iam.workloadIdentityUser \
--member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github/attribute.repository/acme/shop-api"
The attribute condition restricts which repository may use the pool; without it, any repository could exchange a token.
Before you deploy anything: billing
Link a billing account and create a budget alert first:
gcloud billing budgets create --billing-account=0123AB-4567CD-89EFGH \
--display-name="demo-ci monthly" --budget-amount=50USD \
--threshold-rule=percent=0.5 --threshold-rule=percent=0.9 --threshold-rule=percent=1.0
A budget alert sends notifications; it does not stop spending, so pair it with quotas and reviews. Also set up a log sink to a central project, and put the organization-level groups and policies in Terraform so changes are reviewed.