Workspaces vs Directory-per-Environment

Scenario: Development and production genuinely need different provider credentials, different access controls, and a different release cadence - yet the team's single workspace-driven root module has accumulated dozens of if terraform.workspace == "prod" conditionals trying to paper over that reality.

The previous section's warning - that workspaces share backend and credentials by default - points at the real design choice this section is about. Directory-per-environment (or, taken further, separate repositories per environment) creates genuinely distinct root modules: each with its own backend configuration, its own credentials, and its own review/approval controls, wired together by calling the same reusable child modules from Module 6. CLI workspaces minimize duplication, but only really pay off when every environment's topology, credentials, and blast radius genuinely are the same shape - which is a narrower case than teams often assume.

A common, pragmatic middle ground many production teams land on: put the real logic and resource definitions inside well-tested, reusable child modules, and keep each environment's own root as a thin file that mostly just calls that module with environment-specific inputs (shown below). The right choice here comes down to isolation requirements, how much the environments' topologies genuinely diverge, how you promote a change between them, and who owns each one - not simply "which approach produces fewer files to maintain."

Analogy: CLI workspaces are identical apartments on different floors of the same building - same layout, same building security, same landlord. Directory-per-environment is choosing separate buildings entirely. If production genuinely needs its own vault and its own guards that development has no business having access to, it may well need to be a different building, not just a different floor.

A worked configuration

environments/
  dev/main.tf      # calls ../../modules/service
  stage/main.tf
  prod/main.tf
modules/
  service/main.tf

Duplicated environment root directories carry their own risk: without discipline they can drift apart in subtle ways over time (someone tweaks prod/main.tf directly instead of going through the shared module). Keep each environment root genuinely thin, and promote module versions deliberately between environments (the next section covers exactly this) rather than hand-editing each environment's copy independently. Conversely, a single workspace-driven root carrying dense conditional logic hides risky, production-only code paths inside branches that may rarely get tested - constrain that complexity, and make sure every conditional branch actually gets exercised somewhere before it ever reaches production.

Common mistakes in production

Goal: Treat the plan as a change contract: understand every create, update, replacement, and destroy before approving it. Reinforce this lesson in the tf-workspace-environments Terraform lab. Open /labs/terraform and choose slug tf-workspace-environments; the lab runs real Terraform against the offline FakeCloud provider.