Environments — Workspaces vs Directories
The oldest argument in Terraform layout is whether environments should be CLI workspaces — one backend, switchable state, one set of files — or separate directories with separate config and separate state. The argument outlasts most projects because both genuinely work for small cases and both have failure modes that only show up later.
On GCP the question is reframed by the hierarchy. Because the right isolation boundary is a separate project per environment, separate directories — each targeting its own project with its own state — is almost always the answer. Workspaces fit a few narrow cases, and on every other case they bite production. This topic is about which case you are in.
What a Workspace Actually Is
terraform workspace keeps multiple state instances behind one backend and one configuration, switching which state the same code applies to. It is one directory pretending to be many environments, distinguished only by the value of terraform.workspace at runtime. The .tf files are identical across "environments"; the only thing that differs is which state object the backend selects.
That is the whole mechanism, and it explains both its appeal and its danger. The appeal is near-zero ceremony — terraform workspace new staging and you have a second state. The danger is that prod and staging are the same files and the same backend, separated by one selectable variable that a single command can change.
Why Directories Win on GCP
envs/hatch-app-prod/ and envs/hatch-app-staging/ are separate directories with separate backends, each pinned to its own project. The prod project, the prod state object, and the prod IAM policy are physically distinct from staging's. There is no command you can fat-finger that applies staging code to prod, because applying prod means being in the prod directory, which has the prod backend, which writes the prod state, which authenticates to the prod project.
That physical separation is the GCP-shaped advantage. The previous two topics established that an environment maps to a project and a project carries billing, quota, and IAM. Directories make the Terraform boundary line up with the GCP boundary — one directory, one project, one state — so the thing you most want isolated is isolated at every layer at once.
The Prod-Isolation Failure of Workspaces
With workspaces, prod and staging share one backend and one set of .tf files. A terraform workspace select mistake applies the wrong environment, with no second confirmation — one wrong command and production changed. There is no directory you had to be in, no separate backend that would have refused, just a runtime variable that pointed the same code at the wrong state.
The second failure is divergence. Because the config is identical across workspaces, you cannot safely give prod a resource staging lacks — a prod-only firewall rule, a DR replica — without scattering count = terraform.workspace == "prod" ? 1 : 0 conditionals through the code. Every genuine prod/staging difference becomes a conditional, and the configuration slowly drowns in branching that a directory split would have made plain.
Where Workspaces Genuinely Fit
Workspaces fit short-lived, identical, throwaway environments where the whole point is that they are interchangeable. Per-PR ephemeral previews and per-developer sandboxes are the canonical cases: spin one up, test against it, destroy it, and nobody cares which one it was. There the low ceremony of workspace new is exactly the feature, and the absence of per-environment divergence is fine because there is no divergence to express.
The line is durability. If an environment is meant to last, carry distinct config, and absolutely never be confused with another, it wants a directory. If it is meant to be created, used, and thrown away within a pull request, a workspace's lack of isolation is irrelevant because the environment itself is disposable.
The GCP-Specific Tilt
Here is the asymmetry that settles it. Since environments map to projects and projects carry billing, quota, and IAM, the thing you most want isolated — production — is exactly the thing workspaces isolate least. A workspace shares prod's state backend and config with staging; the GCP hierarchy wants prod's project, billing, and IAM completely separate. The two pull in opposite directions precisely where the stakes are highest.
So on GCP the directories-per-project choice is not a stylistic preference but a near-default. HashiCorp's own guidance steers durable multi-environment layouts to directories, and the project hierarchy makes that guidance sharper: map each environment to its own project, give each a directory and a backend, and reserve workspaces for the disposable cases where interchangeability is the goal.
Workspaces — one backend, one config, switchable state, distinguished only by terraform.workspace. Near-zero ceremony, but prod and staging share files and backend, so one workspace select mistake changes the wrong environment. Choose only for short-lived interchangeable environments — per-PR previews, dev sandboxes — where ceremony must be near zero and destruction is expected.
Directories (per project) — separate config, separate backend, separate state, one per environment-project. More files, but the prod project, state, and IAM are physically distinct, so a cross-environment mistake is impossible rather than merely unlikely. Choose for anything durable, and always for prod.
- Using workspaces to separate prod from staging, then applying staging's plan to prod after a forgotten
workspace select— one wrong command, no second confirmation, production changed. - Reaching for workspaces because "the docs mention them first," not realizing HashiCorp's own guidance steers durable multi-environment layouts to directories.
- Trying to give prod a resource staging lacks while sharing one workspace config, and drowning the code in
count = terraform.workspace == "prod" ? 1 : 0conditionals. - Mapping all environments to one project distinguished by workspace, collapsing the GCP isolation boundary so quota and IAM are shared across "environments" that are really one project.
- Promoting a per-PR preview workspace into a long-lived environment, inheriting the shared-backend risk on something that now matters.
- Use a separate directory, backend, and state per environment-project for anything durable, and always for production.
- Reserve
terraform workspacefor short-lived, interchangeable environments — per-PR previews and developer sandboxes — where destruction is expected. - Map each environment to its own GCP project so billing, quota, and IAM isolation are physical, not a runtime variable.
- Keep per-environment config thin and divergence explicit in
.tfvars, rather than encoding environment differences asterraform.workspaceconditionals. - Treat any environment that must never be confused with another as a directory by default, since physical separation makes the mistake impossible.
Knowledge Check
What does a Terraform workspace actually isolate, compared to a directory?
- Only the state instance behind one shared backend and config; a directory isolates config, backend, and state all at once
- Nothing — workspaces and directories are functionally identical and fully interchangeable across every environment scenario
- The provider credentials and service-account keys, while every workspace still shares one common state file
- The target GCP project the apply runs against, while all the workspaces share one single set of .tf files
Why are workspaces specifically dangerous for production on GCP?
- Prod shares one backend and config with staging, so a single
workspace selectmistake applies the wrong environment with no second confirmation - Workspaces cannot authenticate to a production GCP project at all, so prod must be applied entirely by hand from the Console
- GCP bills workspace state objects at a higher per-gigabyte storage rate than the state a directory produces
- Workspaces force you to disable object versioning on the shared state bucket, removing the only rollback safety net you had against a bad apply
Which case genuinely fits a workspace rather than a directory?
- Short-lived, interchangeable environments like per-PR previews or developer sandboxes that are expected to be destroyed
- Long-lived production environments that must never once be confused with staging during an apply
- Any environment that needs a resource the others lack, where the per-environment configs have genuinely diverged in shape
- The org-level IAM and folder scaffolding stack that the whole estate inherits its policy from
Why does the GCP project hierarchy make directories the near-default?
- Environments map to projects carrying billing, quota, and IAM, so the thing you most want isolated — prod — is what workspaces isolate least
- GCP outright forbids using Terraform workspaces together with the GCS remote state backend
- Directories are mandatory for any project that needs to enable more than one GCP API, since workspaces cap you at a single service
- Workspaces only work with the AWS provider and have no support for the google provider at all, so GCP estates are forced onto directories instead
You got correct