Terraform Workspaces & Multi-Environment
Workspaces give a single Terraform configuration multiple isolated state files — one per workspace. The terraform.workspace expression lets configs branch behavior per environment. The exam tests all workspace commands, state storage paths, the default workspace, and the critical tradeoff: workspaces vs directory-per-environment (separate configs). Terraform Cloud workspaces are a distinct, more powerful concept.
1. What are Workspaces?
Workspaces allow a single Terraform configuration directory to maintain multiple independent state files — one per workspace. Each workspace is a completely isolated state environment; resources in one workspace cannot see or affect resources in another.
Key mental model: Workspaces = same config + different state. Resources are managed independently in each workspace even though the .tf files are identical.
2. All Workspace Commands
1# List all workspaces (* marks the currently active one)
2terraform workspace list
3# Output:
4# * default
5# dev
6# staging
7# prod
8
9# Show current workspace name
10terraform workspace show
11# Output: prod
12
13# Create a new workspace (and switch to it)
14terraform workspace new dev
15terraform workspace new staging
16terraform workspace new prod
17
18# Switch to an existing workspace
19terraform workspace select prod
20terraform workspace select default
21
22# Delete a workspace (must have empty state — no resources)
23terraform workspace delete dev
24
25# Create without switching (Terraform >= 1.1)
26terraform workspace new --no-color uatExam tip:
terraform workspace newboth creates and switches to the new workspace.terraform workspace selectonly switches.
3. The default Workspace
- Named
default— always exists - Cannot be deleted
- State file location:
terraform.tfstate(local) or the backend's base key (remote) - If you never create other workspaces, you are always in
default
1terraform workspace show # → default
2terraform workspace list # → * default4. State File Paths
Workspaces keep state separate by using distinct file paths:
| Backend | Workspace | State Path |
|---|---|---|
| Local | default | terraform.tfstate |
| Local | dev | terraform.tfstate.d/dev/terraform.tfstate |
| Local | prod | terraform.tfstate.d/prod/terraform.tfstate |
| S3 | default | <key> (e.g., prod/app/terraform.tfstate) |
| S3 | dev | env:/dev/<key> |
| S3 | prod | env:/prod/<key> |
| Terraform Cloud | any | Separate workspace = separate state |
1# Local state directory structure after creating dev and prod workspaces:
2.
3├── terraform.tfstate ← default workspace
4├── terraform.tfstate.d/
5│ ├── dev/
6│ │ └── terraform.tfstate ← dev workspace
7│ └── prod/
8│ └── terraform.tfstate ← prod workspace5. Using terraform.workspace in Configuration
The terraform.workspace expression returns the name of the current workspace as a string:
1# Simple conditional on resource size
2resource "aws_instance" "web" {
3 ami = data.aws_ami.ubuntu.id
4 instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
5
6 tags = {
7 Name = "${terraform.workspace}-web-server"
8 Environment = terraform.workspace
9 }
10}
11
12# Conditional resource count
13resource "aws_instance" "web" {
14 count = terraform.workspace == "prod" ? 3 : 1
15 instance_type = "t3.micro"
16}
17
18# Using workspace in naming to avoid conflicts
19resource "aws_s3_bucket" "app" {
20 bucket = "my-app-${terraform.workspace}-data"
21 # dev → my-app-dev-data
22 # prod → my-app-prod-data
23}6. Workspace Variable Maps Pattern
A common pattern is a local map keyed by workspace name, which avoids long chains of conditionals:
1locals {
2 # Define all environment-specific values in one place
3 env_config = {
4 default = {
5 instance_type = "t3.micro"
6 instance_count = 1
7 db_class = "db.t3.micro"
8 multi_az = false
9 }
10 dev = {
11 instance_type = "t3.micro"
12 instance_count = 1
13 db_class = "db.t3.micro"
14 multi_az = false
15 }
16 staging = {
17 instance_type = "t3.small"
18 instance_count = 2
19 db_class = "db.t3.small"
20 multi_az = false
21 }
22 prod = {
23 instance_type = "t3.medium"
24 instance_count = 4
25 db_class = "db.t3.medium"
26 multi_az = true
27 }
28 }
29
30 # Look up current workspace's config (fallback to dev if unknown workspace)
31 config = lookup(local.env_config, terraform.workspace, local.env_config["dev"])
32}
33
34resource "aws_instance" "web" {
35 count = local.config.instance_count
36 instance_type = local.config.instance_type
37}
38
39resource "aws_db_instance" "main" {
40 instance_class = local.config.db_class
41 multi_az = local.config.multi_az
42}This pattern is clean, readable, and scales well as environments grow.
7. Workspaces vs Directory-per-Environment
This is a critical exam topic — know when to use each approach:
| Aspect | Workspaces | Directory per Environment |
|---|---|---|
| Config sharing | Same files for all envs | Separate files per env |
| State isolation | Full — separate state file | Full — separate state file |
| Config isolation | None — one change affects all | Full — independent configs |
| Environment differences | Must use conditionals / local maps | Separate tfvars or configs |
| Blast radius of bug | All workspaces share the bug | Only affected env |
| Refactoring overhead | Low — change once | High — change in N directories |
| Visibility | Harder — must know current workspace | Clear — directory shows env |
| Best for | Truly identical infra, different sizes | Infra that varies significantly |
| Terraform recommend | Simple cases | Production-grade multi-env |
When workspaces work well:
- All environments are structurally identical (only size/count differs)
- Small teams where shared config is manageable
- Testing or ephemeral environments (feature branches, PRs)
When directory-per-environment is better:
- Environments have meaningfully different resources (prod has WAF, dev doesn't)
- You need separate state backends per environment
- Strict change control (prod changes must be explicit)
- Large teams where accidental cross-environment changes are risky
8. CI/CD Workflow with Workspaces
1# Typical CI/CD pipeline using workspaces
2
3# Step 1: Select or create the workspace for this deployment target
4terraform workspace select prod || terraform workspace new prod
5
6# Step 2: Plan with workspace-specific var file
7terraform plan -var-file="environments/prod.tfvars" -out=prod.tfplan
8
9# Step 3: Apply the saved plan
10terraform apply prod.tfplan
11
12# Step 4: Show current workspace in output
13echo "Deployed to workspace: $(terraform workspace show)"1# Feature branch workflow — ephemeral workspace per PR
2BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD | tr '/' '-')
3terraform workspace new $BRANCH_NAME || terraform workspace select $BRANCH_NAME
4terraform apply -auto-approve
5
6# After PR merge — clean up
7terraform destroy -auto-approve
8terraform workspace select default
9terraform workspace delete $BRANCH_NAME9. Terraform Cloud Workspaces — Important Distinction
Terraform Cloud workspaces are fundamentally different from CLI workspaces:
| Aspect | CLI Workspaces | Terraform Cloud Workspaces |
|---|---|---|
| Scope | State isolation within one config | Full execution environment |
| Config | Shared .tf files | Can have different VCS repos |
| Variables | terraform.workspace in code | Per-workspace variable sets |
| Execution | Local machine | Remote execution in TC runners |
| Access control | File system | Per-workspace teams/permissions |
| Recommended for | Simple multi-state | All production use cases |
In Terraform Cloud, each workspace is more like a complete deployment environment — with its own:
- State file
- Variables and secrets
- VCS connection (branch/repo)
- Run history and approval gates
- Access controls (who can plan vs apply)
1# Connecting to Terraform Cloud (replaces backend "s3")
2terraform {
3 cloud {
4 organization = "my-company"
5
6 workspaces {
7 name = "production-app" # single named workspace
8 # OR
9 tags = ["app", "production"] # all workspaces with these tags
10 }
11 }
12}10. Multi-Account AWS Pattern
For strict environment isolation, use separate AWS accounts (not just workspaces):
1# provider.tf — provider per AWS account
2
3# Dev account
4provider "aws" {
5 alias = "dev"
6 region = "us-east-1"
7 assume_role {
8 role_arn = "arn:aws:iam::111111111111:role/TerraformRole"
9 }
10}
11
12# Prod account
13provider "aws" {
14 alias = "prod"
15 region = "us-east-1"
16 assume_role {
17 role_arn = "arn:aws:iam::999999999999:role/TerraformRole"
18 }
19}1# Backend with separate state per account+environment
2# environments/prod/backend.tf
3terraform {
4 backend "s3" {
5 bucket = "prod-account-terraform-state"
6 key = "app/terraform.tfstate"
7 region = "us-east-1"
8 # This bucket lives in the prod AWS account
9 }
10}11. Quick Reference
| Concept | Key Fact |
|---|---|
terraform workspace new X | Creates AND switches to workspace X |
terraform workspace select X | Switches to existing workspace X |
terraform workspace list | Lists all workspaces; * = current |
terraform workspace show | Prints current workspace name |
terraform workspace delete X | Deletes X (must have empty state) |
| default workspace | Always exists; cannot be deleted |
terraform.workspace | Expression returning current workspace name (string) |
| Local state path | terraform.tfstate.d/<name>/terraform.tfstate |
| S3 state path | env:/<name>/<key> for non-default workspaces |
| Workspaces isolate | State only — config is shared |
| Workspace best for | Identical infra at different scales |
| Directory-per-env best for | Different infra per environment |
| TC workspaces vs CLI | TC workspaces = full execution env; CLI = state isolation only |
Practice Questions3
Q1. What command creates a new Terraform workspace named `staging` and switches to it?
Select one answer before revealing.
Q2. How can you reference the current Terraform workspace name within your configuration to set different resource sizes per environment?
Select one answer before revealing.
Q3. What is the key limitation of using Terraform workspaces for environment management?
Select one answer before revealing.