01 / 09
GCP / 01

GCP foundations

Google Cloud makes sense the moment you accept one idea: the project is the unit of everything. Billing attaches to projects. APIs are switched on per project. IAM policies bind to projects. Quotas are counted per project. Once that clicks, the rest of the platform is mostly vocabulary. This page gives you the working map: the resource hierarchy, project identity, billing accounts, regions and zones, the gcloud tool and its two kinds of credentials, and a translation table from GCP service names to the AWS ones you may already know. It ends with a lab you can run in a terminal in ten minutes.


The project is the unit of everything

If you come from AWS, your instinct is to look for the account. AWS organises the world around accounts: an account is a billing boundary, an IAM boundary, a quota boundary, and usually a blast-radius boundary, and companies end up running dozens or hundreds of them stitched together with AWS Organizations. Google Cloud took a different bet. There is one login (your Google identity), and underneath it the platform is carved into projects. A project is not a folder for tidiness. It is the object that owns your resources, the object your bill is itemised against, the object each API is enabled on, the object IAM policies bind to, and the object quotas are counted against. When something in GCP confuses you, the first question to ask is always "which project am I in?", and the answer fixes a surprising fraction of problems.

This is the deepest structural difference between the two clouds, so it is worth spelling out the consequences. In AWS, spinning up an isolated environment means creating a new account, which is heavyweight enough that teams plan it. In GCP, creating a project takes one command and a few seconds, so projects are cheap and plentiful: one per environment, one per team, one per experiment, sometimes one per microservice. A single engineer might touch ten projects in a day. The second consequence is that resource names only need to be unique within a project (with a few global exceptions like bucket names), so the project id becomes part of how you address things. The third is that deleting a project deletes everything in it, which makes cleanup after an experiment genuinely trivial; you do not hunt down stray resources the way you do in a shared AWS account.

A useful mental model: an AWS account is a house you move into; a GCP project is a labelled box you can make, fill, and throw away. Both clouds give you a way to group the units (Organizations in AWS, the resource hierarchy here), but the grain of the unit is different, and the grain shapes everything about how teams work.

The resource hierarchy

Projects do not float free. GCP arranges everything into a tree with four kinds of node, and the tree is the backbone for both organisation and access control.

organizationexample.comfolder: engineeringfolder: datacheckout-prodcheckout-devwarehouse-prodvmbucketcloud sqlvpcIAM policy inherits downwardorganization → folders → projects → resources; grants made high up apply to everything below
The four levels of the hierarchy. Resources always live in exactly one project; projects sit under folders or directly under the organization.

At the top sits the organization, which represents your company and is tied to a Google Workspace or Cloud Identity domain. You get exactly one per domain, and it is created automatically the first time a user from that domain makes a project. Under it come folders, which exist purely to group things: by department, by team, by environment, whatever matches your org chart or your trust boundaries. Folders can nest up to ten levels deep. Under folders come projects, and inside projects live the actual resources: VMs, buckets, databases, networks, subscriptions, keys. Every resource has exactly one parent project; there is no such thing as an orphaned VM.

Two practical notes. First, personal accounts (a plain Gmail login) have no organization at all, and their projects hang in space with no parent. That is fine for learning, and it is the setup the lab at the bottom of this page assumes. Second, the hierarchy is not decoration. It is the rail that IAM policies and organization policies ride down: a role granted on a folder applies to every project inside it, and a constraint set at the organization (say, "no public buckets") binds every descendant. We will come back to that below, and a full treatment of cloud identity lives in its own page.

Project identity: id, number, and name

Every project carries three identifiers, and mixing them up is a classic first-week stumble. The project name is a display label, freely editable, and means nothing to any API. The project id is the one you actually use: a globally unique, human-chosen string like checkout-prod-417, six to thirty characters, lowercase letters, digits and hyphens, and immutable forever once created. It appears in gcloud commands, in resource URLs, in service account email addresses, and in bucket conventions, so choose it like you would choose a domain name. The project number is a machine-assigned integer like 384925017246; you rarely type it, but it shows up in default service account names and some API responses, so learn to recognise it as "also my project."

Because project ids are globally unique across every Google Cloud customer, the obvious ones are long gone; a common pattern is a team prefix plus a random suffix. And because they are immutable, deleting a project does not free its id immediately. Deletion itself is soft: the project enters a pending-deletion state for roughly thirty days, during which it can be restored, and only afterwards is it purged. That grace period has saved more than one team from a fat-fingered teardown, but it also means a hastily deleted id cannot be reused on the spot.

Switching between projects is an everyday act in GCP in a way it never is with AWS accounts. In the console there is a project picker in the header bar; on the command line the active project is just a piece of gcloud configuration, set with one command. Most gcloud commands also accept an explicit --project flag, which is the safer habit in scripts: depending on ambient configuration is how a cleanup script ends up emptying the wrong environment.

What hangs off a project

Here is the picture to hold in your head. A project is a container, and four separate control systems all key off it: the API switchboard, the IAM policy, the quota ledger, and the billing link. The first three live inside the project; billing is an external object the project points at.

project: checkout-prod-417number 384925017246 · id immutableenabled APIscompute.googleapis.comstorage.googleapis.comsqladmin.googleapis.comIAM policyalice → roles/editorci-sa → roles/run.admin+ inherited from abovequotasCPUs us-central1: 24/72IP addresses: 4/8API reads/min: 3k/60kresources3 VMs · 2 buckets1 Cloud SQL · 1 VPClabels: env=prod, team=paybilling account01AB23-CD45EF-6789GHpays for many projectslinkedAPIs, IAM, quotas and resources live in the project; the billing account is outside, merely linked
One project, four control systems. Notice the billing account sits outside the box: it is a separate resource with its own permissions.

This is why the project is such a clean isolation unit. Disable an API and nothing in the project can call it. Revoke someone's role on the project and they lose access to all of it at once. Hit a quota and only that project is throttled; your other environments carry on. Unlink billing and everything that costs money stops (more on that shortly). In AWS, each of these has its own machinery (service control policies, IAM boundaries, service quotas per account, consolidated billing); in GCP they all converge on the same object, which keeps the mental model small.

Billing accounts are separate objects

A common early surprise: creating a project does not set up payment. A billing account is its own resource, with its own id (a triplet like 01AB23-CD45EF-6789GH), its own IAM roles, and its own lifecycle. It holds the payment instrument and the invoice settings, and projects are linked to it, many-to-one: one billing account typically pays for dozens of projects, while each project links to at most one billing account at a time. The bill you receive is itemised per project (and, with billing export turned on, per label, which is why labels matter).

The separation is deliberate. The people who manage money are rarely the people who create infrastructure, so the roles are split: a billing administrator manages the account itself, while an engineer needs only the lightweight billing-user role on the account to link new projects to it. In an enterprise, finance owns one or two billing accounts and engineering creates projects against them; nobody has to share a credit card.

Two behaviours to remember. A project with no linked billing account is not dead, but it is limited to free operations: you can hold IAM policies and use a handful of always-free services, and anything that costs money fails with an error telling you billing is disabled. And unlinking billing from a live project does not pause resources politely; services start shutting things down, and some data can be deleted. Unlinking is teardown, not a pause button. The lab at the end uses linking and unlinking deliberately, so you will see both sides.

APIs are off until you turn them on

Here is the second early surprise, and the source of the most common beginner error message in all of GCP. Every service (Compute Engine, Cloud Storage, Pub/Sub, all of them) sits behind an API named like a hostname: compute.googleapis.com, storage.googleapis.com, pubsub.googleapis.com. In a fresh project, nearly all of them are disabled. Call one anyway and you get a 403 with the words "API has not been used in project ... before or it is disabled", plus a link to fix it. Read that error once and you will recognise it forever.

Enabling is per project and takes one command: gcloud services enable compute.googleapis.com. The console enables some APIs implicitly when you click through a service's page for the first time, which is convenient but hides the mechanism; infrastructure-as-code tools like Terraform make you declare enablements explicitly, which is the honest version. Enabling an API can also have side effects: turning on Compute Engine, for example, creates a default service account and (unless your organization forbids it) a default network in the project. Enablement is not always a pure flag flip.

Why design it this way? Partly safety: a project that can only call the three APIs it needs has a smaller attack surface, and audit becomes "list the enabled services" rather than archaeology. Partly hygiene: the enabled-API list is an honest inventory of what a project actually does. In AWS, every service endpoint in every region is live in every account from day one and you constrain usage with IAM policy; GCP gives you a hard off switch one level before IAM is even consulted.

Habit worth forming. When any GCP call fails strangely in a new project, check three things in order: which project you are pointed at, whether the API is enabled there, and whether billing is linked. Those three checks resolve most first-month mysteries.

Regions and zones

Google's physical footprint is organised the same way AWS's is, with one naming quirk and one genuinely interesting detail. A region is an independent geographic area: us-central1 is Iowa, europe-west1 is Belgium, asia-south1 is Mumbai. Each region contains several zones, named by appending a letter: us-central1-a, us-central1-b, and so on. A zone is the unit of failure you plan around: it is a slice of data-centre capacity with its own power and networking, and a zonal outage takes out the resources in that zone while its siblings keep running. Zones within a region talk to each other over low-latency links (round trips well under a couple of milliseconds), which is what makes synchronous replication across zones practical; replication across regions is a different, slower, more deliberate business.

region: us-central1 (Iowa)zone-azone-bzone-fregion: europe-west1 (Belgium)zone-bzone-czone-dzonal: VM, persistent disk · regional: subnet, Cloud SQL HA · global: VPC, bucket names, many load balancerszones inside a region share fast links; regions are independent failure domains
Regions contain zones; every resource type is zonal, regional, or global, and you should know which before you depend on it.

The interesting detail is what a zone name actually means. A zone is an abstraction, not the name of a building. Behind each zone sits one or more physical clusters, and Google assigns the mapping from zone letter to physical cluster per project, to spread load. Your us-central1-a and another customer's us-central1-a may be entirely different hardware, and even two of your own projects can map the same letter to different clusters. The practical consequence: putting two projects "in the same zone" does not guarantee physical co-location, and the folk wisdom of "everyone defaults to zone a, so it is crowded" mostly does not survive contact with how the mapping really works.

The other thing to internalise early is the zonal / regional / global split, because GCP has more global resources than AWS does. A VM and its persistent disk are zonal. A subnet is regional. But a VPC network is global (one network can have subnets in every region, a real difference from AWS that the VPC and networking page covers properly), bucket namespaces are global, and the flagship load balancers are global with a single anycast IP. When you read any GCP resource's documentation, "what scope does this live at" should be the first question you answer.

gcloud: the one tool

Nearly everything in GCP is driven by one CLI, gcloud, shipped as part of the Google Cloud SDK alongside two siblings you will meet later (gsutil, the older storage tool, now largely replaced by gcloud storage, and bq for BigQuery). The command structure is consistent enough to guess: gcloud <service> <resource> <verb>, as in gcloud compute instances list or gcloud projects create. Appending --help anywhere in that chain gives you a man page, and --format=json turns any command's output into something a script can parse.

gcloud keeps state in named configurations. A configuration is a small bag of properties: which account you are logged in as, which project is active, default region and zone for compute, and so on. gcloud init walks you through creating the first one; gcloud config set project my-proj-id changes the active project; gcloud config list shows where you currently stand. The feature worth adopting on day one is multiple configurations: gcloud config configurations create work and gcloud config configurations activate work let you keep, say, a personal setup and a work setup (different Google accounts, different default projects) and flip between them atomically instead of mutating one config and forgetting what you changed. It is the equivalent of AWS CLI named profiles, but switching is stateful rather than per-command.

One warning that saves real pain: defaults are conveniences for humans, not contracts for scripts. Anything you automate should pass --project explicitly, because the ambient configuration on a CI runner or a colleague's laptop will not match yours, and "worked on my machine, deleted things on yours" is a genuinely bad afternoon.

Two kinds of credentials: you, and your code

gcloud authentication trips up almost everyone once, because there are two separate credential stores that look the same. gcloud auth login authenticates the CLI: it opens a browser, you sign in, and from then on gcloud commands act as you. That credential is used by gcloud and nothing else. Separately, gcloud auth application-default login creates application default credentials (ADC): a credential file that Google's client libraries (in Python, Go, Java, Node, all of them) discover and use when your code calls GCP APIs from your machine. Run the first and your own scripts using the client libraries will still fail with authentication errors; run the second and gcloud itself is unaffected. Two stores, two commands.

ADC is worth understanding properly because it is the mechanism that makes code portable across environments. When a client library starts up, it looks for credentials in a fixed order: first the GOOGLE_APPLICATION_CREDENTIALS environment variable pointing at a key file, then the ADC file that gcloud auth application-default login wrote (under ~/.config/gcloud/), and finally, if running on Google infrastructure, the metadata server, an internal endpoint every VM and Cloud Run instance can reach that hands out short-lived tokens for the service account attached to that workload. The payoff: the same code, with zero credential plumbing, uses your identity on your laptop and the workload's service identity in production. No keys in the repository, no environment branching. When a tutorial tells you to download a service account JSON key and export the path, treat that as the legacy fallback; on real infrastructure the metadata server makes key files unnecessary, and most security teams now actively forbid them.

Labels: the metadata you will wish you had

A label is a key-value pair you attach to projects and to most resources: env=prod, team=payments, cost-centre=cc-1142. Keys and values are lowercase, limited in length, and capped at 64 per resource. Labels do nothing by themselves, which makes them easy to dismiss, and then two months later finance asks what the payments team spent in April and the answer depends entirely on whether you labelled things. Labels flow into the billing export, so cost can be sliced by any label dimension; they also drive filtering in the console, in gcloud (--filter="labels.env=prod"), and in bulk operations. The discipline that works is boring: agree a small fixed vocabulary (environment, team, owner, cost centre), apply it at project creation, and let project-level labels do most of the work since per-project cost attribution is usually what people want anyway. One distinction to keep crisp: labels are not IAM. They never grant or deny anything (network tags on VMs, a different mechanism with a confusingly similar name, do affect firewall rules; the networking page untangles that).

Quotas: the limits you find by hitting them

Every project has quotas: caps on how much of each resource it can hold and how fast it can call each API. They come in two flavours. Allocation quotas cap concurrent usage, like the number of vCPUs per region or in-use IP addresses; exceed one and creation requests fail until something is freed or the quota is raised. Rate quotas cap API calls per minute and refill continuously; exceed one and you get 429s and should back off. Quotas are counted per project, usually per region, which is another reason project-level isolation is real: a runaway batch job in the data team's project cannot starve the checkout project's quota.

Quotas exist to protect Google's capacity and to protect you from yourself (a fork-bombing Terraform loop hits the ceiling long before it hits your credit limit). New projects start with deliberately low limits, and some resources (GPUs are the famous case) start at zero, full stop, until you request an increase. Increases are requested per quota per region from the console's quota page, are usually granted quickly for modest asks, and can take days with questions for large ones. The operational lesson: before a launch or a load test, check the quota page for the project, because quota exhaustion at peak looks exactly like an outage and has a much more embarrassing root cause.

How IAM rides the hierarchy

A short preview here, because access control deserves its own deep treatment alongside the identity page. GCP IAM answers "who can do what on which resource" with bindings: a member (a user, group, or service account) is bound to a role (a named bundle of permissions like roles/storage.objectViewer) on some node of the resource hierarchy. And this is where the tree earns its keep: policies inherit downward. A role granted on the organization applies to every folder, project, and resource beneath it; a role granted on a folder covers all its projects; a role granted on a project covers everything inside. The effective policy on any resource is the union of the policies on the resource and all of its ancestors.

Inheritance is additive and, historically, there was no subtraction: a grant made high in the tree could not be cancelled by anything below it (GCP has since added deny policies for exactly this gap, but additive-by-default is still the right mental model). The design consequence is that the shape of your hierarchy is your access-control architecture. Granting the data team a role on the data folder gives them every current and future project under it, which is exactly what you want when a new project appears weekly, and exactly what you do not want if production secrets share a folder with sandboxes. Teams that structure folders around trust boundaries (prod versus non-prod above all else) get IAM policies that stay short; teams that mirror the org chart blindly end up with sprawling per-project grants anyway. How this compares with AWS's policy-document model, and where service accounts fit, is coming in the dedicated pages.

The service-name decoder

GCP's service names are mostly literal descriptions, which is refreshing after AWS's brand names, but you still need the mapping in your head to transfer what you already know. This table covers the ten you will meet first. Equivalence is approximate; each pair differs in ways that matter once you go deep (Cloud Storage has no S3-style per-bucket region quirks but has its own storage-class behaviour, GCP's load balancer is global where ALB is regional, and so on), but as a first index into your existing knowledge it works.

GCPAWSWhat it is
Compute EngineEC2Virtual machines, disks, instance groups
Cloud Storage (GCS)S3Object storage with global bucket namespace
Persistent DiskEBSNetwork-attached block storage for VMs
VPCVPCPrivate networking; GCP's is global, AWS's is regional
Cloud SQLRDSManaged Postgres, MySQL, SQL Server
GKEEKSManaged Kubernetes
Cloud RunFargate / App RunnerRun containers without managing nodes
Cloud FunctionsLambdaEvent-driven functions
Pub/SubSNS + SQS / KinesisMessaging: topics, subscriptions, streaming ingestion
BigQueryRedshift / AthenaServerless analytics warehouse
Cloud Monitoring / LoggingCloudWatchMetrics, logs, alerting

The reverse mapping, plus the places where the analogy actively misleads, is covered from the other side in AWS foundations. The two pages are meant to be read as mirrors: whichever cloud you know, the other page translates for you.

CLI lab: a project, cradle to grave

Theory done; now run it. This lab exercises everything above in about ten minutes: you will create a project, link billing, enable an API, set defaults, create and use a real bucket, and then tear the whole thing down. You need the Google Cloud SDK installed and a Google account with a billing account on it (the free trial gives you one with credits, and everything below fits comfortably inside the free tier anyway). Read each command before running it; the point is to watch each concept become a real object.

# 0. Sign in. This authenticates gcloud itself (opens a browser).
gcloud auth login

# 1. Create a project. Ids are globally unique and immutable,
#    so add a random suffix to avoid collisions.
export PROJECT_ID=foundations-lab-$RANDOM
gcloud projects create $PROJECT_ID --name="gcp-foundations-lab"
gcloud projects describe $PROJECT_ID   # note the projectNumber

# 2. Point gcloud at it. Every later command uses this default.
gcloud config set project $PROJECT_ID
gcloud config list                     # confirm account + project

# 3. Link billing. List your billing accounts, then link by id.
gcloud billing accounts list
gcloud billing projects link $PROJECT_ID \
  --billing-account=01AB23-CD45EF-6789GH   # use YOUR id from the list

# 4. Enable an API. Fresh projects have almost nothing switched on.
gcloud services list --enabled         # short list: the bare plumbing
gcloud services enable storage.googleapis.com
gcloud services list --enabled         # storage has appeared

# 5. Set default region and zone so compute commands stop asking.
gcloud config set compute/region us-central1
gcloud config set compute/zone us-central1-a

# 6. Create a bucket, use it, inspect it. Bucket names are global,
#    so the project id makes a handy unique prefix.
gcloud storage buckets create gs://$PROJECT_ID-scratch \
  --location=us-central1
echo "hello from the foundations lab" > hello.txt
gcloud storage cp hello.txt gs://$PROJECT_ID-scratch/
gcloud storage ls gs://$PROJECT_ID-scratch/
gcloud storage cat gs://$PROJECT_ID-scratch/hello.txt

# 7. Teardown. Delete the bucket, unlink billing, delete the project.
#    Project deletion is soft: ~30 days to change your mind.
gcloud storage rm -r gs://$PROJECT_ID-scratch
gcloud billing projects unlink $PROJECT_ID
gcloud projects delete $PROJECT_ID --quiet
gcloud projects list --filter="lifecycleState:DELETE_REQUESTED"
rm hello.txt

A few things worth noticing as you go. Step 1's describe shows all three identifiers from earlier on one screen. If you skip step 3 and jump straight to step 6, the bucket creation fails with a billing-disabled error, which is a cheap way to see the billing-link mechanism enforce itself. Step 4's before-and-after listing makes the API switchboard concrete. And the final projects list shows your project sitting in its thirty-day pending-deletion state rather than simply vanishing. Total cost of the lab if you tear down promptly: effectively zero.

Further reading

Found this useful?