Getting the Google Cloud hierarchy right: folders, projects and org policies

In GCP the project is the blast radius and the folder is where policy lives. Most estates we see have neither used deliberately. Here is the structure we deploy and the eight policies we set on day one.

Google Cloud gives you a stronger organisational model than AWS did for years, and most teams use about a third of it. Projects get created ad hoc from the console, everything lands directly under the organisation node, and IAM is granted per project by whoever needed access that day.

The hierarchy is not paperwork. It is where inheritance happens, and inheritance is the only thing that keeps a growing estate governable.

The structure

Organisation → folders by environment and domain → projects per workload.

We usually deploy four top-level folders:

  • bootstrap — the seed project holding Terraform state, the service accounts that create everything else, and nothing that a workload touches. Access here is break-glass only.
  • common — shared services: the Shared VPC host project, Artifact Registry, the logging and monitoring projects, the Cloud Build or CI project.
  • production — a subfolder per domain (payments, analytics), and a project per workload inside it.
  • non-production — the same shape, with looser policy and a hard cap on spend.

The project stays the unit of blast radius. One workload, one environment, one project. Quotas, IAM, billing and most APIs are per project, so a project shared by four services means four services share a quota ceiling and an IAM surface.

Sandbox projects for engineers go in their own folder with a budget alert, a 30-day lifecycle and no connectivity to the Shared VPC. Give people somewhere legitimate to experiment or they will experiment in staging.

The eight org policies we set first

Org policy constraints apply at organisation or folder level and inherit down. These eight remove the majority of what a posture scan would otherwise find:

  1. compute.requireShieldedVm — secure boot and vTPM on every VM.
  2. compute.vmExternalIpAccess — deny by default, allow-list the handful of projects that genuinely need a public IP.
  3. sql.restrictPublicIp — Cloud SQL instances get private IP only.
  4. storage.publicAccessPrevention — enforced, organisation-wide. This is the GCP equivalent of S3 Block Public Access and it should never be off.
  5. iam.allowedPolicyMemberDomains — restricts IAM grants to your own Cloud Identity domain. This single constraint stops the "someone shared a bucket with their personal Gmail" class of incident.
  6. iam.disableServiceAccountKeyCreation — the most valuable one on this list. Long-lived JSON service account keys are the credential that leaks, and Workload Identity Federation removes the need for them.
  7. compute.restrictVpcPeering and compute.vmCanIpForward — keep the network topology under the network team's control.
  8. gcp.resourceLocations — pin resources to the regions you operate in, which is both a latency and a data-residency control.

Apply them at the organisation node with exceptions at folder level, not the reverse. A policy with twenty project-level exceptions is not a policy.

IAM that survives growth

Three rules that keep it tractable:

Grant to groups, never to users. Every binding in Terraform references a Google Group synced from your identity provider. Joining a team becomes a group membership, and offboarding becomes one action instead of forty.

Grant at the smallest scope that works, and prefer predefined roles over basic ones. roles/editor on a project is close to admin and includes the ability to grant itself more. It is the most common finding in a GCP review.

Use short-lived credentials everywhere. Workload Identity Federation for CI and for workloads outside GCP, Workload Identity for GKE pods, and service account impersonation for humans who need elevated access. If a .json key file exists anywhere in your estate, that is the ticket to open first.

Billing and labels

One billing account, billing export to BigQuery from day one, and a label policy enforced in Terraform: every project carries env, owner and cost-centre. Without the export you cannot answer a cost question with SQL; without the labels the answer has a large "unattributed" row that nobody owns. The same problem shows up on every cloud — it is step one of the AWS bill piece and it is identical here.

All of this is Terraform, applied from the bootstrap project by a pipeline. Project creation in particular should never be a console action, because a console-created project has no owner, no labels and no baseline. It is one of the first things we fix in a cloud engagement.

What to do this week

Open the organisation policy page and check three constraints: iam.disableServiceAccountKeyCreation, storage.publicAccessPrevention and iam.allowedPolicyMemberDomains. If any is not enforced, you have found this week's work, and all three are reversible in a click if something breaks.

ConsultorIA

Want this done on your cloud?

A ten-day read-only assessment is free, and Skyline lets you see your estate on a map before you write to us.

Related articles

Modules people reuse instead of copying

The two failures are a module that wraps one resource and adds nothing, and a module that does everything and nobody dares change. A minimal interface, safe defaults and honest versioning are what separate them.