Problem Context
You have a single Terraform configuration and you need to deploy it to dev, staging, and production with different variable values. The temptation is to use Terraform workspaces โ a built-in feature for maintaining multiple state files from a single configuration. But workspaces are frequently misunderstood and misused, leading to environments that are supposed to be isolated but share more than they should.
Understanding when workspaces are the right tool โ and when separate directories or backends serve you better โ is one of the most important Terraform architectural decisions for multi-environment infrastructure.
- You're about to use workspaces for dev/staging/prod but aren't sure if that's the right approach
- A workspace plan showed it would delete production resources because you were in the wrong workspace
- Your dev workspace shares some resources with production and you don't know which
- You're not sure how workspaces relate to Terraform Cloud environments
Know when to use workspaces โ and when not to. Both decisions matter.
How Workspaces Work
Terraform workspaces maintain separate state files for the same configuration directory. The default workspace is called default. Every terraform apply modifies the state of the current workspace only.
# List workspaces
terraform workspace list
# Create and switch to a new workspace
terraform workspace new staging
# Switch to an existing workspace
terraform workspace select production
# Show current workspace
terraform workspace show
# Delete a workspace (must be empty of resources)
terraform workspace delete stagingYou can reference the current workspace inside your configuration:
locals {
is_production = terraform.workspace == "production"
}
resource "azurerm_service_plan" "main" {
name = "asp-myapp-${terraform.workspace}"
sku_name = local.is_production ? "P2v3" : "B2"
...
}Workspace-Based Environment Strategy
Workspaces work well for ephemeral, short-lived environments โ feature branch environments, review apps, temporary testing environments. Each developer creates a workspace, deploys, and destroys it when done:
# CI: create feature branch environment
WORKSPACE="feature-${PR_NUMBER}"
terraform workspace new $WORKSPACE || terraform workspace select $WORKSPACE
terraform apply -var="environment=$WORKSPACE" -auto-approve
# CI: destroy on PR merge
terraform workspace select $WORKSPACE
terraform destroy -auto-approve
terraform workspace select default
terraform workspace delete $WORKSPACEWhen NOT to Use Workspaces for Environments
HashiCorp itself recommends against using workspaces as a primary environment promotion mechanism. The reasons:
- Single backend: All workspace state files are in the same storage location. A misconfigured IAM policy can expose production state to dev users.
- Shared provider configuration: All workspaces use the same
providers.tf. You can't easily use different service principals per environment. - Human error:
terraform workspace select production && terraform applylooks identical to dev commands. It's easy to run apply against the wrong workspace. - Configuration parity: It's hard to guarantee that dev and prod use the same code version when you're switching workspaces in a single directory.
The Alternative: Directory-Per-Environment
infra/
โโโ modules/
โ โโโ app-service/
โ โโโ networking/
โโโ environments/
โ โโโ dev/
โ โ โโโ main.tf # Calls modules
โ โ โโโ backend.tf # Separate state per env
โ โ โโโ terraform.tfvars # Dev-specific values
โ โ โโโ providers.tf # Dev service principal
โ โโโ staging/
โ โ โโโ ...
โ โโโ production/
โ โโโ ...Benefits: strict isolation at the filesystem level, separate backends (separate IAM for state access), clear blast radius, independent deployment pipelines.
# Different state keys for complete isolation
# environments/production/backend.tf
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstate001"
container_name = "tfstate"
key = "production/main.tfstate"
}
}
# environments/dev/backend.tf
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstate001"
container_name = "tfstate"
key = "dev/main.tfstate"
}
}Terraform Cloud Workspaces
Terraform Cloud workspaces are a different concept from CLI workspaces. Each Terraform Cloud workspace has its own:
- State file
- Variables (including secrets)
- Run history
- Access controls
- VCS trigger configuration
Terraform Cloud workspaces map well to environments โ they're the right place for dev/staging/prod isolation.
# Connecting a directory to a Terraform Cloud workspace
# terraform.tf
terraform {
cloud {
organization = "myorg"
workspaces {
name = "myapp-production" # One workspace per environment
}
}
}Decision Guide
Use workspaces when:
โ
Creating short-lived feature/preview environments
โ
Testing configuration changes before promoting to long-lived envs
โ
Environments are structurally identical, just need separate state
Use separate directories when:
โ
Long-lived environments (dev, staging, prod)
โ
Environments need different provider configs or service principals
โ
Environment state must be strictly isolated for compliance
โ
Different teams own different environmentsProduction Checklist
- Do not use CLI workspaces for long-lived dev/staging/prod environments โ use separate directories
- Use workspaces for ephemeral PR/feature environments where parity with prod is required
- Always display the current workspace in your terminal prompt when working with Terraform
- Add workspace name validation in
variables.tfif using workspaces for environments - In CI, set
TF_WORKSPACEenvironment variable explicitly rather than relying on local state - For Terraform Cloud, create one workspace per environment with separate variable sets per workspace

