Workspaces, Environments, and the Monorepo vs Polyrepo Question
What This Concept Is
Two overlapping layout questions that every Terraform team eventually argues about.
The environment question. You have dev, staging, and prod. How do you organize state and config so changes can progress from one to the next without risk of cross-environment damage?
Three common layouts:
- CLI workspaces -- one config, multiple named states (
terraform workspace new prod). Isolated state per environment, shared code. - Directory per environment --
envs/dev/,envs/staging/,envs/prod/, each with its own backend config and its own state. Shared modules inmodules/. - Repo per environment -- separate git repositories for each env. Hard isolation; hardest to keep in sync.
The repo question. Do you keep all infrastructure code in one monorepo (modules + envs + pipelines) or split it across many repos (one per team, one per service, or one per stack)?
These are not the same question, but they interact: workspaces work best inside a single repo; directory-per-env works in mono and poly equally; repo-per-env forces poly.
Why It Matters Here
Teams that get this wrong pay for it forever:
- All environments share one state -> one mis-typed
-targetdestroys prod instead of dev - Prod config drifted three versions behind dev because the two repos evolved independently
- Workspaces used for "team A's infra" and "team B's infra" instead of environments -> credential confusion, cross-team state access
Decide early and write the choice down in an ADR (see Semester 7, Module 5).
Concrete Example
Directory-per-environment inside a monorepo (common production pattern):
infra/
modules/
vpc/
ecs-service/
rds-postgres/
envs/
dev/
main.tf # calls modules/vpc with dev inputs
backend.tf # S3 bucket=acme-tfstate-dev, key=infra/dev.tfstate
terraform.tfvars
staging/
main.tf
backend.tf
terraform.tfvars
prod/
main.tf
backend.tf
terraform.tfvars
Each envs/<env> has its own state, its own credentials scope, and its own apply cadence. Promotion from dev to prod is a PR that edits a module version pin in envs/prod/main.tf -- explicit and reviewable. The modules are tested once in dev before the pin is bumped in prod.
CLI workspaces (rare in production):
terraform workspace new dev
terraform workspace new prod
terraform workspace select prod
terraform apply
The config references the current workspace:
locals {
env = terraform.workspace
}
resource "aws_s3_bucket" "artifacts" {
bucket = "acme-artifacts-${local.env}"
}
This works and is terse. But it hides a dragon: if you forget to switch workspaces and apply, you just applied prod changes to dev (or worse, the reverse). HashiCorp's own documentation warns against using workspaces for decomposition into separate deployment credentials -- which is exactly what environments are.
Monorepo vs Polyrepo Tradeoffs
| Dimension | Monorepo | Polyrepo |
|---|---|---|
| Module change reaches all callers | One PR, visible | Bump version pins per repo |
| Access control | Coarse (repo-level) | Fine-grained per repo |
| Blast radius of one PR | Can touch everything | Bounded by repo |
| Consistency / standards | Easy to enforce | Drifts between repos |
| Onboarding | One clone | Many clones |
| Refactoring large surfaces | Possible | Painful |
| CI cost / clarity | One pipeline, clever filtering | Many pipelines, clear per-stack |
There is no universal answer. Teams under ~30 infrastructure engineers often prefer monorepos; very large orgs or heavily regulated environments move to polyrepo for access control.
Common Confusion / Misconception
"Workspaces are the HashiCorp-recommended way to do environments." They are not. The official docs explicitly say workspaces "alone are not a suitable tool for system decomposition, because each subsystem should have its own separate configuration and backend." Use directory-per-env instead.
"Monorepo = slower CI." Only if you do not use path filtering. A well-configured pipeline only plans the environments that changed in a given PR.
"Polyrepo = better isolation." Better access isolation, yes. But polyrepo environments drift as teams fork modules and forget to merge back. Isolation through discipline (branch protections, required reviews) usually beats isolation through repo boundaries.
How To Use It
- Default to directory-per-environment with a monorepo of modules. This is the industry "boring production" answer.
- Put each environment's backend config in its own
backend.tf-- never share a state key across environments. - Use workspaces only for ephemeral state (PR previews, per-developer sandboxes), never for long-lived environments.
- If you inherit a workspace-per-env setup, migrate environment by environment; don't try a big-bang refactor.
- Document the layout in the repo's root
README.md. New engineers should find the answer in 30 seconds.
Check Yourself
- Why is using
terraform.workspaceto distinguish prod from dev risky in practice? - In a directory-per-env monorepo, how do you promote a module change from dev to prod?
- Give one specific scenario where polyrepo is clearly the right answer.
Mini Drill or Application
Sketch (on paper or in a markdown file) how you would lay out a small team's infrastructure:
- one platform team of 4 engineers
- three environments (dev, staging, prod)
- two services (web, worker), each with its own VPC, ECS cluster, and RDS
- shared modules
Produce the directory tree, state locations, and one paragraph explaining why you did not use workspaces.
See also (external)
- Terraform Language: Workspaces -- what they do and, importantly, what they should not be used for.
- Terraform Best Practices: Environments and folders -- Babenko's layout guidance including per-env directories and shared modules.
Source Backbone
Infrastructure-as-code details are tool-specific, but these local books provide the operational backbone for shell, Git, and change discipline.
- Pro Git - versioned infrastructure changes, branching, review, and rollback habits.
- Git from the Bottom Up - mental model for stateful change history.
- The Linux Command Line - shell and automation grounding for infrastructure work.