Terraform Workspaces & Environments
Almost every team runs the same infrastructure more than once: dev, staging, and production, maybe per region or per customer. Terraform offers several ways to do that from shared code. CLI workspaces keep several state files for one configuration. Directory-per-environment layouts give each environment its own root module that calls shared modules. Platforms like HCP Terraform, Terragrunt, and Terraform Stacks add orchestration on top.
The word "workspace" is confusingly overloaded: CLI workspaces and HCP Terraform workspaces are different things. And the most convenient option isn't always the safest. This page explains the approaches and when each fits.
TL;DR
- CLI workspaces (
terraform workspace new staging) give one configuration multiple separate state files, withterraform.workspaceavailable in code. - They suit near-identical, short-lived copies (feature environments, test stacks). They're risky for dev versus prod, because everything shares one backend and it's easy to apply to the wrong one.
- Directory-per-environment (
envs/prod,envs/staging) calling shared modules is the most common production layout: explicit, isolated, and reviewable. .tfvarsfiles hold per-environment values; keep differences in data, not in branching logic.- HCP Terraform workspaces are separate configurations with their own state, variables, permissions, and run history.
- Terragrunt and Stacks reduce duplication when you have many environments, regions, or accounts.
Quick Example
A directory-per-environment layout with shared modules:
And the CLI workspace alternative for ephemeral copies:
Core Concepts
CLI Workspaces
Every configuration starts in the default workspace. Creating another workspace creates a separate state in the same backend. With S3, that's under a path like env:/staging/<key>. In code, terraform.workspace returns the current name:
Limitations:
- Same backend, same credentials for every workspace, so it's hard to put prod in a separate account with separate permissions.
- Invisible context: the active workspace isn't in the code or the diff, so it's easy to apply to prod thinking you're in staging.
- Identical structure: environments can't meaningfully diverge without conditionals everywhere.
HashiCorp's own guidance: CLI workspaces aren't a suitable isolation mechanism for environments that need strong separation, such as production versus development.
Directory per Environment
Each environment is its own root module with its own backend configuration, variables, and credentials. Shared logic lives in modules, and environment roots are thin wiring. Benefits:
- Isolation: separate states, backends, and cloud accounts; a mistake in staging can't touch prod.
- Visibility: the environment is in the file path, so pull requests show exactly which environment changes.
- Controlled promotion: bump a module version in
staging, verify, then bump it inprod.
The cost is some duplication across environment directories, which tools like Terragrunt reduce.
Variables and tfvars
Keep environment differences in values, not logic: instance sizes, counts, CIDRs, and feature toggles in terraform.tfvars (loaded automatically) or -var-file=prod.tfvars. Branching on environment names in modules (count = var.env == "prod" ? 3 : 1) spreads environment knowledge everywhere. Pass an explicit replica_count variable instead.
HCP Terraform Workspaces
In HCP Terraform (formerly Terraform Cloud) and Terraform Enterprise, a workspace is a managed unit with its own state, variables (including sensitive ones), VCS connection, run history, permissions, and policies. Typically there's one workspace per environment × component, closer to directory-per-environment than to CLI workspaces. Projects group workspaces, and run triggers chain them.
Terragrunt and Stacks
- Terragrunt wraps Terraform/OpenTofu to keep configurations DRY: shared backend and provider settings are generated from parent
terragrunt.hclfiles, dependencies between components are declared, andrun --allapplies many units in order. - Terraform Stacks (HCP Terraform) define components once and deployments (environments, regions, accounts) declaratively, orchestrating plans and applies across them.
Choosing an Approach
Best Practices
Separate Production Credentials
Production should live in its own cloud account or project, applied by a pipeline identity that only has prod access. That's much easier with separate root configurations than with workspaces sharing one backend and role.
Make the Target Environment Obvious
Put the environment in the directory path, the backend key, resource names, and tags. In CI, derive the environment from the path being changed, not from a manually selected workspace.
Promote Changes Through Environments
Pin module versions per environment and upgrade them in order: dev → staging → prod. Each environment then runs a known, tested version, and rollback is a version revert.
Clean Up Ephemeral Environments
Automate terraform destroy plus workspace deletion when a pull request closes, and run periodic sweeps for leftovers. Forgotten preview environments are a classic cloud cost leak.
Common Mistakes
Applying to the Wrong Workspace
Mitigate with a shell prompt showing the workspace, CI-only applies, and separate credentials per environment so a staging identity can't modify prod.
Environment Logic Scattered Through Modules
Copy-Pasted Environments Drifting Apart
Duplicating full configurations per environment without shared modules means fixes land in one environment and not the others. Keep environment directories thin and move real logic into versioned modules.
FAQ
Should I use Terraform workspaces for dev, staging, and prod?
Usually not with CLI workspaces. They share a backend and credentials, and the active workspace is easy to get wrong. Separate root configurations per environment, calling shared modules, give stronger isolation and clearer reviews. CLI workspaces shine for temporary, near-identical copies.
Are HCP Terraform workspaces the same as CLI workspaces?
No. An HCP Terraform workspace is a full managed unit with its own state, variables, permissions, and runs, typically one per environment and component. CLI workspaces are just multiple state files for one working directory.
How do I share values between environments?
Mostly you shouldn't; environments should be independent. For shared infrastructure (a central DNS zone, a shared network), read values through data sources or a parameter store rather than coupling states directly.
Is Terragrunt still needed?
It remains popular for large estates with many accounts and regions, though native features (moved/import blocks, terraform test, HCP Terraform Stacks) have narrowed the gap. Small and medium setups often do fine with directories, modules, and a CI pipeline.
Related Topics
- Terraform — The tool overview
- Terraform State — What each workspace or environment stores
- Terraform Modules — Shared code across environments
- Terraform Testing — Verifying changes before promotion
- GitOps — Environment promotion via Git
- Deployment Strategies — Promotion and rollback in general