---
title: "Define tier membership"
canonical_url: https://ruslanakchurin.dev/blog/making-iac-boring/03-membership
description: "Environment boundaries blur when workload-specific members (service accounts, IAM bindings, DNS records) accumulate in the shared environment tier."
datePublished: 2026-05-24
dateModified: 2026-06-25
series: making-iac-boring
seriesName: "Making IaC boring"
tags: ["infrastructure as code","identity and access management","google cloud","multi-tenancy","service accounts"]
about: [{"name":"Identity management","@type":"Thing","sameAs":"https://www.wikidata.org/wiki/Q977772"}]
mentions: [{"name":"Google Cloud Platform","@type":"Thing","sameAs":"https://www.wikidata.org/wiki/Q17054505"},{"name":"Amazon Web Services","@type":"Thing","sameAs":"https://www.wikidata.org/wiki/Q456157"},{"name":"Domain Name System","@type":"Thing","sameAs":"https://www.wikidata.org/wiki/Q8767"}]
position: 3
---
# Define tier membership

_How tiers get populated._

The membership trap starts when workload boundaries collapse into one shared project.

I use GCP vocabulary in this part: projects, folders, service accounts, and IAM bindings. The same structure maps to AWS accounts and roles, other clouds, and self-managed platforms with their own isolation and deployment surfaces.

> [!NOTE]
> Security ambiguity made this shape unsafe for me. It was painful too: shared projects turn simple releases into archaeology. Once unrelated workloads started accumulating service accounts, IAM bindings, DNS records, and {{release exceptions | Short-lived release changes tied to one workload: a DNS cutover, a temporary IAM grant, migration access, or a rollout rule that should disappear with the service. | Release exceptions}} inside the same project, the boundary stopped enforcing separation. We had to keep proving it by convention inside the boundary.

Teams reach for a shared project when cross-project IAM feels harder than putting services together.

That shape gets expensive after the second workload. Those objects change with the workload, but they are written into the same shared project. The environment tier starts carrying one binding per service and one temporary override per deployment. It stops being a landing zone and becomes the slowest workload catalogue in the system.

```svg path="making-iac-boring/diagrams/04-shared-wrong.svg" width="360"
environment/
  workload-api service account
  workload-api IAM binding
  workload-worker service account
  workload-worker IAM binding
  api.example.com record
  worker.example.com record

workload-api/
  service

workload-worker/
  service
```

Everything looks close to the shared project, so environment absorbs it. The cost is {{ownership drift | The resource lives near the platform, but the release belongs to the workload. That mismatch is what pulls unrelated reviews and migrations into the same change. | Ownership drift}}: an application release needs environment review, and deleting one workload becomes a shared-project migration.

```svg path="making-iac-boring/diagrams/05-shared-clean.svg" width="360"
organisation/
  workload folder
  domain ownership
  DNS zone delegation

environment/
  workload project
  CI/CD OIDC trust
  deploy identity
  deployment binding rules

workload-api/
  assigned project reference
  service
  application service account
  workload-specific bindings
  delegated DNS records

workload-worker/
  assigned project reference
  service
  application service account
  workload-specific bindings
  delegated DNS records
```

Organisation owns the logical container before any environment exists: the GCP folder where workload projects will live. It also owns domain ownership and DNS zone delegation, the record that says which lower layer is authoritative for a zone.

Environment provisions a workload project inside that folder and attaches it to the environment boundary. It creates the deployment access path into that project: CI/CD OIDC trust, the deploy identity, and the IAM binding rules that identity is allowed to apply. Then it publishes the {{assigned project reference | The environment owns the project boundary and publishes the reference upward. The workload consumes that reference instead of discovering or creating the project itself. | Assigned project reference}}.

The workload consumes that reference and creates the application service account, workload-specific bindings, delegated DNS records, and release resources inside the assigned project.

If the workload creates the project, its deploy identity needs authority over the environment or organisation layer. If environment has to discover workload-created projects after the fact, environment now depends on workload-published identity: an output, a label, an inventory query, or a registry entry. All of those shapes reverse the graph. The lower tier must publish the project reference; the higher tier consumes it.

> [!NOTE]
> In AWS, the same boundary is usually an account, OU, VPC, or route-domain rather than a GCP project. The provider noun changes; the rule stays the same: environment creates the workload boundary and publishes the reference upward.

Environment is the catalogue of assigned workload projects. The drift starts when that catalogue also becomes the place for workload internals: application service accounts, local IAM bindings, release resources, and delegated records. Once the project exists, those members belong with the workload and move with the application.

A shared environment is a poor default when cross-project IAM feels hard. With a proper tiering model, delegating a whole workload project is simpler than proving safe separation inside a shared one.

## Members are lifecycle units

The provider's noun does not decide the tier; the owning lifecycle does. A member is any object a tier owns as part of that lifecycle: a cloud resource, a DNS zone, or a SaaS configuration object.

Environment owns the network because the network exists before workloads and many workloads attach to it. Its contract should expose the usable shape of that network: private subnets, intended routes, and allowed attachment surfaces. It should not enumerate every service that might consume it.

Identity follows the same rule. Organisation owns identity roots and broad policy boundaries. Environment owns federated access for deploys into that environment. An application service account belongs with the workload when its lifecycle follows the application. The workload carries that service account and declares the access it needs; IAM bindings against organisation or environment surfaces stay with the tier that owns that surface.

## Population tests

The membership decision has to protect the graph before it optimises for convenience:

- Which owner applies and repairs this without pulling unrelated releases into the same change?
- Which surface does this object mutate?
- Is the consumer declaring a need, or trying to apply a change outside its boundary?
- Would this object still exist if the consumer that named it disappeared?
- Does placing it here force a lower tier to read higher-tier outputs?
- If removal is destructive, which tier stages consumer detachment first?

Leaving the decision implicit lets provider APIs choose the ownership boundary by default.

A lower tier should know the classes of consumers it supports, not maintain a growing hand-authored inventory of every consumer instance. When environment contains bespoke bindings, records, and listener rules for each service, environment review widens with every release and environment apply carries workload-specific failure modes.

Declarations belong higher when the workload owns the change. The mutation stays lower when the lower tier owns the shared surface, and the contract has to say how consumers attach and detach safely.

Resolver and binding rules need to be designed before stacks depend on each other through outputs. The next part moves from membership to enforcement: where the release path refuses invalid composition before any provider call runs.
