CoursesInfrastructure as Code & automationIntermediate · Terraform at scale

Workspaces & environments

dev, staging, prod from one codebase.

Intermediate12 min · lesson 11 of 23

Real systems have multiple environments — dev, staging, production — that should be near-identical but separate, and a core IaC skill is managing them from one codebase without copy-paste. There are two main approaches, and knowing the tradeoff matters. Terraform workspaces let one configuration hold multiple independent states (one per workspace), switching between them; the more common and robust approach is separate directories or state files per environment, each calling the same modules with different inputs. Both give you "one codebase, many environments"; they differ in isolation.

terminal
# approach 1: workspaces — one config, separate state per workspace
$ terraform workspace new staging
$ terraform workspace select prod
$ terraform workspace list
default
staging
* prod
# terraform.workspace is available in code, e.g. to size things per env

The directory-per-environment pattern

The pattern most teams prefer for production is a directory per environment, each with its own backend/state and a small root config that calls shared modules with environment-specific inputs. This gives strong isolation — prod and dev have entirely separate state, so a mistake in dev cannot possibly touch prod, and you can grant different access to each. The shared logic lives in modules; the per-environment directories are thin, just wiring the modules with the right variables. It is more files than workspaces but far clearer blast-radius boundaries.

directory layout
modules/ # shared, reusable logic
network/
web/
environments/
dev/
main.tf # calls modules with dev inputs; own backend/state
terraform.tfvars # env = "dev", instance sizes, etc.
prod/
main.tf # same modules, prod inputs; SEPARATE state
terraform.tfvars # env = "prod"

Variables per environment

The differences between environments — instance sizes, replica counts, domain names — live in variables, supplied per environment via .tfvars files (dev.tfvars, prod.tfvars) or CI variables. The code is identical; only the inputs change, so staging genuinely mirrors production and the only differences are the ones you explicitly declared. This is what makes environments trustworthy: when the code is shared and only reviewed variables differ, "it worked in staging" actually means something for production.

Workspaces are NOT strong isolation between dev and prod
A common mistake is using Terraform workspaces to separate production from development and assuming they are safely isolated — they share the same backend and configuration, so a wrong workspace selection or a config error can affect the wrong environment, and they encourage subtle env-specific conditionals that are hard to review. For genuinely separate blast radii (dev vs prod), prefer separate state/directories with separate backends and access controls. Workspaces are fine for lightweight variations; they are not a security boundary between environments.