Workspaces & Environments
Most infrastructure exists in multiple environments: at minimum a staging area and production. Terraform workspaces give you a way to manage several environments from one configuration by giving each its own isolated state file, while sharing the same backend.
What Are Workspaces?
A workspace is a named slot for a state file within the same backend. Every Terraform configuration starts in the default workspace. When you create a new workspace, Terraform creates a separate state file for it — the configuration code stays the same, but the infrastructure tracked in state is completely independent.
You cannot delete the default workspace. Most teams treat default as production or avoid it entirely in favour of explicitly named workspaces.
Workspaces solve a specific problem: you want to spin up an identical copy of infrastructure (e.g., a staging environment) without duplicating your .tf files. The tradeoff is that all environments share the same configuration code, which means any change applies to all of them (after separate applies).
Workspace Commands
All workspace operations go through terraform workspace:
| Command | What it does |
|---|---|
terraform workspace list | List all workspaces; current one is marked with * |
terraform workspace show | Print the name of the current workspace |
terraform workspace new <name> | Create a workspace and switch to it |
terraform workspace select <name> | Switch to an existing workspace |
terraform workspace delete <name> | Delete a workspace (must be empty — no managed resources) |
# Start in default — create staging and production workspaces
terraform workspace new staging
# Created and switched to workspace "staging"!
terraform workspace new production
# Created and switched to workspace "production"!
terraform workspace list
# default
# staging
# * production
terraform workspace select staging
# Switched to workspace "staging".
After switching workspaces, the next plan or apply reads and writes the state for that workspace only. Operations on one workspace have no effect on any other.
State Isolation
On a local backend, Terraform stores workspace state files under terraform.tfstate.d/<workspace-name>/terraform.tfstate. On an S3 backend, the key is prefixed:
# S3 key for default workspace
env:/default/prod/networking/terraform.tfstate
# S3 key for staging workspace
env:/staging/prod/networking/terraform.tfstate
# S3 key for production workspace
env:/production/prod/networking/terraform.tfstate
The prefix is configurable via the workspace_key_prefix argument on the S3 backend (default: env:).
State locking uses the full S3 key as the DynamoDB LockID. You do not need separate tables per workspace — the same table handles locks for every workspace in the configuration.
Environment-Specific Config
The current workspace name is available in configuration as terraform.workspace. Use it to vary resource names, sizes, or counts per environment:
locals {
env = terraform.workspace # "staging" or "production"
instance_type = {
staging = "t3.small"
production = "m5.large"
}
min_capacity = {
staging = 1
production = 3
}
}
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = local.instance_type[local.env]
tags = {
Name = "app-${local.env}"
Environment = local.env
}
}
resource "aws_autoscaling_group" "app" {
min_size = local.min_capacity[local.env]
max_size = local.min_capacity[local.env] * 4
# ...
}
This pattern lets staging use cheaper resources without separate configuration files. The map lookup fails loudly if an unexpected workspace name is used — which is a feature, not a bug.
Variable Files per Workspace
Rather than embedding environment differences in locals, many teams use per-workspace .tfvars files and pass the right one at apply time:
# staging.tfvars
instance_type = "t3.small"
min_capacity = 1
domain = "staging.example.com"
# production.tfvars
instance_type = "m5.large"
min_capacity = 3
domain = "example.com"
# Apply with the matching var file
terraform workspace select staging
terraform apply -var-file="staging.tfvars"
terraform workspace select production
terraform apply -var-file="production.tfvars"
In CI/CD pipelines, derive the var file name from the workspace automatically:
#!/bin/bash
WORKSPACE=$(terraform workspace show)
terraform apply -var-file="${WORKSPACE}.tfvars" -auto-approve
*.tfvars files with secrets
Variable files often contain environment-specific values that are sensitive (API keys, database passwords). Add them to .gitignore and supply them through CI/CD secret management instead.
Workspaces & Remote Backends
All major backends support workspaces natively. The S3, GCS, Azure Blob, and Consul backends prefix the state key. HCP Terraform treats each workspace as a first-class object with its own settings, variables, and run history.
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "networking/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-locks"
# workspace_key_prefix defaults to "env:" — override if needed
workspace_key_prefix = "workspace"
}
}
With this config and two workspaces, state files land at:
workspace/staging/networking/terraform.tfstateworkspace/production/networking/terraform.tfstate
When Not to Use Workspaces
Workspaces are convenient but have real limitations. The Terraform documentation itself recommends against using workspaces for strong environment isolation:
All workspaces use the same provider credentials, the same backend access, and the same configuration code. If your staging and production environments need different IAM roles, different cloud accounts, or meaningfully different resource topologies — use separate root configurations instead of workspaces.
| Use workspaces when… | Use separate configs when… |
|---|---|
| Environments are structurally identical (just different sizes) | Environments need different resource types or topologies |
| Same cloud account is acceptable for all envs | Environments live in different accounts or subscriptions |
| You want quick ephemeral environments (feature branches) | Production needs strict access controls separate from staging |
| Small team, simple infrastructure | Compliance requirements mandate environment separation |
A common real-world pattern: use workspaces for short-lived feature-branch environments, and use separate root configurations (with shared modules) for the permanent staging and production environments.
Key Takeaways
- Workspaces give each environment its own state file while sharing the same backend and configuration code.
- Use
terraform.workspacein configuration to vary resource names, sizes, and counts per environment. - Per-workspace
.tfvarsfiles keep environment differences explicit and auditable — pass the right file at apply time. - Remote backends (S3, GCS, Azure Blob) prefix the state key per workspace; one DynamoDB table handles locking for all.
- Workspaces are not a substitute for strong environment isolation. When environments need different accounts, different access controls, or different topologies, use separate root configurations with shared modules.
- The
defaultworkspace always exists and cannot be deleted — treat it as production or name your workspaces explicitly from the start.