What Are Terraform Workspaces?
Workspaces let you use the same Terraform configuration to manage multiple environments — each with its own state file.
Same .tf files → different state files → different infrastructure
workspace "dev" → terraform.tfstate.d/dev/terraform.tfstate → dev infra
workspace "staging" → terraform.tfstate.d/staging/terraform.tfstate → staging infra
workspace "prod" → terraform.tfstate.d/prod/terraform.tfstate → prod infra
Instead of duplicating code into dev/, staging/, prod/ directories, workspaces let you switch environments with one command.
Quick Start
# Create workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# Switch to dev
terraform workspace select dev
terraform apply
# Switch to prod
terraform workspace select prod
terraform apply
Each apply creates separate infrastructure managed by separate state.
Workspace Commands
| Command | Description |
|---|---|
terraform workspace list | List all workspaces |
terraform workspace show | Show current workspace |
terraform workspace new NAME | Create a new workspace |
terraform workspace select NAME | Switch to a workspace |
terraform workspace delete NAME | Delete a workspace |
$ terraform workspace list
default
dev
* staging
prod
The * marks the current workspace.
Using terraform.workspace in Configuration
The terraform.workspace variable returns the current workspace name. Use it to vary configuration per environment:
Instance Sizing
resource "aws_instance" "app" {
ami = var.ami_id
instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
tags = {
Name = "app-${terraform.workspace}"
Environment = terraform.workspace
}
}
Variable Maps per Environment
locals {
instance_type = {
dev = "t3.micro"
staging = "t3.small"
prod = "t3.large"
}
instance_count = {
dev = 1
staging = 2
prod = 3
}
}
resource "aws_instance" "app" {
count = local.instance_count[terraform.workspace]
ami = var.ami_id
instance_type = local.instance_type[terraform.workspace]
tags = {
Name = "app-${terraform.workspace}-${count.index}"
Environment = terraform.workspace
}
}
Resource Naming
resource "aws_s3_bucket" "data" {
bucket = "myapp-data-${terraform.workspace}"
}
resource "aws_db_instance" "main" {
identifier = "myapp-db-${terraform.workspace}"
# ...
}
Each workspace creates uniquely-named resources: myapp-data-dev, myapp-data-staging, myapp-data-prod.
Workspaces with Remote Backends
Workspaces work with all remote backends. Each workspace gets its own state file:
S3 Backend
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "app/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
# Workspaces store state at:
# app/env:/dev/terraform.tfstate
# app/env:/staging/terraform.tfstate
# app/env:/prod/terraform.tfstate
}
}
S3 automatically prefixes the key with env:/WORKSPACE_NAME/.
Terraform Cloud
terraform {
cloud {
organization = "my-org"
workspaces {
tags = ["app"]
}
}
}
In Terraform Cloud, workspaces are a first-class concept with their own variables, runs, and permissions.
Complete Example: Multi-Environment VPC
# variables.tf
variable "ami_id" {
type = string
}
locals {
config = {
dev = {
vpc_cidr = "10.0.0.0/16"
instance_type = "t3.micro"
instance_count = 1
}
staging = {
vpc_cidr = "10.1.0.0/16"
instance_type = "t3.small"
instance_count = 2
}
prod = {
vpc_cidr = "10.2.0.0/16"
instance_type = "t3.large"
instance_count = 3
}
}
env = local.config[terraform.workspace]
}
# main.tf
resource "aws_vpc" "main" {
cidr_block = local.env.vpc_cidr
tags = {
Name = "vpc-${terraform.workspace}"
Environment = terraform.workspace
}
}
resource "aws_subnet" "app" {
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(local.env.vpc_cidr, 8, 1)
tags = {
Name = "app-subnet-${terraform.workspace}"
}
}
resource "aws_instance" "app" {
count = local.env.instance_count
ami = var.ami_id
instance_type = local.env.instance_type
subnet_id = aws_subnet.app.id
tags = {
Name = "app-${terraform.workspace}-${count.index}"
Environment = terraform.workspace
}
}
# outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
}
output "instance_ips" {
value = aws_instance.app[*].private_ip
}
Deploy:
terraform workspace select dev
terraform apply -var="ami_id=ami-12345678"
terraform workspace select prod
terraform apply -var="ami_id=ami-12345678"
Workspaces vs Directory Structure
Two approaches to managing multiple environments:
Workspaces Approach
project/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfstate.d/
├── dev/
├── staging/
└── prod/
Pros: No code duplication, easy to switch, consistent config. Cons: Same code must work for all environments, easy to apply to wrong workspace.
Directory Approach
project/
├── modules/
│ └── app/
├── dev/
│ ├── main.tf
│ └── terraform.tfvars
├── staging/
│ ├── main.tf
│ └── terraform.tfvars
└── prod/
├── main.tf
└── terraform.tfvars
Pros: Clear separation, different configs, harder to accidentally apply to wrong env. Cons: Code duplication, harder to keep in sync.
When to Use Workspaces
- Environments are similar (same resources, different sizes)
- Small to medium projects
- Solo developer or small team
- Same AWS account for all environments
When to Use Directories
- Environments are different (prod has extra services, monitoring, etc.)
- Strict access control per environment
- Different AWS accounts per environment
- Large teams with separate responsibilities
- Need different Terraform versions per environment
Safety Tips
Don’t Forget Which Workspace You’re In
Add the workspace to your shell prompt:
# In ~/.bashrc or ~/.zshrc
export PS1="\$(terraform workspace show 2>/dev/null) \w $ "
Or check before every apply:
echo "Current workspace: $(terraform workspace show)"
terraform plan
Protect Production
# Prevent accidental destroy in prod
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = terraform.workspace == "prod" ? true : false
}
}
Note: prevent_destroy doesn’t support expressions directly. Use a variable instead:
variable "protect_resources" {
type = bool
default = false
}
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = true # Set per-workspace via tfvars
}
}
Use Workspace-Specific Variable Files
terraform apply -var-file="${terraform.workspace}.tfvars"
# Or in a script:
terraform apply -var-file="$(terraform workspace show).tfvars"
Common Errors
“Workspace does not exist”
Error: Currently selected workspace "staging" does not exist.
Create it first: terraform workspace new staging
“default” workspace can’t be deleted
The default workspace always exists and cannot be deleted. Switch to a named workspace instead.
State conflicts between workspaces
Each workspace has isolated state. If you see resources from another workspace in your plan, check that you’re in the correct workspace: terraform workspace show.
Hands-On Courses
Learn by doing with interactive courses on CopyPasteLearn:
Conclusion
Terraform workspaces let you manage dev, staging, and prod with the same .tf files and separate state. Use terraform.workspace to vary instance sizes, counts, and naming. Use workspaces when environments are similar; use separate directories when they’re fundamentally different. Always check terraform workspace show before applying to production.




