Terraform workspaces vs directories for environments
When workspaces help and when separate directories are safer for dev, staging, and prod — with the tradeoffs.
Terraform workspaces are alternate state instances inside the same backend configuration. terraform workspace select staging switches which state file Terraform reads — typically the same key prefix with a env:/staging/ path segment — without changing your .tf files. They are lightweight and built-in. They are also easy to misuse: one fat-fingered workspace select prod followed by apply with dev variables has deleted production more than once.
This note compares workspaces to the directory-per-environment pattern, shows how state keys differ, and gives a decision framework for when each approach fits. If workspaces feel abstract, solidify Terraform state concepts first — workspaces are just namespaced state, not magic environments.
Workspaces share code and backend config; directories share code via modules but isolate state paths and CI triggers.
How workspaces partition state
With an S3 backend, the default workspace uses terraform.tfstate as the key; named workspaces use env:/NAME/terraform.tfstate. All workspaces share the same backend block in code — only the active workspace changes which object Terraform reads. terraform.workspace is available in HCL for conditional logic, but heavy branching on workspace name in modules is a smell.
Shell prompts that show the active workspace (PS1 with terraform workspace show) have prevented more prod accidents than any Terraform feature. CI should pass -var-file=prod.tfvars explicitly rather than relying on a workspace selector hidden in the pipeline script. If your pipeline runs apply without echoing workspace name in the job log, fix that before the next outage postmortem.
terraform {backend "s3" {bucket = "acme-tfstate"key = "network/terraform.tfstate"region = "eu-west-1"}}# staging state object: env:/staging/network/terraform.tfstate
Directory-per-environment alternative
Separate folders — live/prod/vpc, live/staging/vpc — each with its own backend key and often its own CI pipeline or approval gate. Code reuse comes from calling the same git-tagged module, not from switching workspaces. Prod gets a different AWS account, stricter IAM, and manual approval; dev does not share a workspace selector with prod at all.
Terragrunt and similar wrappers generate backend keys from folder paths automatically — you get directory isolation without copy-pasting key = values. The mental model stays simple: cd into the folder, run plan, the state path is implied by where you stand. That beats remembering whether you are in workspace prod or workspace production.
module "vpc" {source = "git::https://git.example.com/modules/vpc.git?ref=v2.1.0"name = "prod"cidr = "10.10.0.0/16"}# State key: prod/vpc/terraform.tfstate — no workspace switch required
terraform workspace list* defaultterraform workspace select stagingterraform plan -var-file=staging.tfvarsAlways verify workspace name before applyWhere this goes next
Whichever pattern you pick, lock state remotely, scan plans with Checkov, and pin module versions. At scale, Terragrunt generates backend keys from folder paths so you get directory isolation without copy-paste. The Terraform course compares workspaces, -var-file, and multi-account layouts in practice.
Anti-pattern: one workspace per customer in multi-tenant SaaS — state files grow unbounded and blast radius is unclear. Prefer a directory or stack per tenant with explicit backend keys when tenant infra is long-lived.
Go deeper in a courseTerraformState, workspaces, modules, and safe multi-environment workflows.View course