CloudOpsGuide
terraform

Terraform Workspaces: Multi-Environment Deployments

Intermediate
12 minutes
October 2026
CloudOpsGuide Team

Terraform Workspaces: Multi-Environment Deployments

Manage dev, staging, and production with Terraform workspaces — commands, patterns, and best practices for environment isolation.

Table of Contents

What Are Workspaces

Terraform workspaces let you manage multiple states within a single configuration. Each workspace gets its own state file, but shares the same .tf code.

Think of it as: same recipe, different kitchens.

terraform.workspace = "dev"      → terraform.tfstate.d/dev/terraform.tfstate
terraform.workspace = "staging"  → terraform.tfstate.d/staging/terraform.tfstate
terraform.workspace = "prod"     → terraform.tfstate.d/prod/terraform.tfstate

Workspace Commands

# List workspaces (default is "default")
terraform workspace list

# Create a workspace
terraform workspace new staging

# Switch workspaces
terraform workspace select dev

# Show current workspace
terraform workspace show

# Delete a workspace (state must be empty)
terraform workspace delete staging

Environment Pattern

Simple Setup

variable "environment" {
  type    = string
  default = "dev"
}

locals {
  env = terraform.workspace == "default" ? "dev" : terraform.workspace

  # Environment-specific config
  instance_count = {
    dev     = 1
    staging = 2
    prod    = 5
  }

  vm_size = {
    dev     = "Standard_B2s"
    staging = "Standard_D2s_v5"
    prod    = "Standard_D4s_v5"
  }
}

resource "azurerm_linux_virtual_machine" "app" {
  name                = "app-${local.env}"
  size                = local.vm_size[local.env]
  instance_count      = local.instance_count[local.env]
  resource_group_name = "myapp-${local.env}-rg"
  # ...
}

Per-Environment Variable Files

environments/
├── dev.tfvars
├── staging.tfvars
└── prod.tfvars
# environments/dev.tfvars
instance_count = 1
vm_size        = "Standard_B2s"
enable_ha      = false
# environments/prod.tfvars
instance_count = 5
vm_size        = "Standard_D4s_v5"
enable_ha      = true

Apply:

terraform workspace select prod
terraform apply -var-file="environments/prod.tfvars"

Backend Configuration per Workspace

terraform {
  backend "azurerm" {
    resource_group_name  = "tfstate-rg"
    storage_account_name = "tfstatestg"
    container_name       = "tfstate"
    key                  = "terraform.tfstate"    # auto-suffixed by workspace
  }
}

State paths become:

  • terraform.tfstate.d/dev/terraform.tfstate
  • terraform.tfstate.d/prod/terraform.tfstate

Conditional Resources

resource "azurerm_monitor_autoscale_setting" "prod" {
  count = terraform.workspace == "prod" ? 1 : 0
  # ... only exists in production
}

resource "azurerm_key_vault_secret" "dev_override" {
  count = terraform.workspace == "dev" ? 1 : 0
  # ... only in dev
}
locals {
  # Scale resources by environment
  node_count = terraform.workspace == "prod" ? 10 : 1

  # Different VM images per environment
  image_reference = terraform.workspace == "prod" ? {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts-gen2"
  } : {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-focal"
    sku       = "20_04-lts-gen2"
  }
}

Limitations

1. Same Backend for All Environments

All workspaces share the same backend config — you can't use different state backends for dev vs prod.

2. No Workspace-Scoped Access Control

Anyone with backend access can see all workspaces. Prod and dev share the same permissions.

3. Easy to Apply to Wrong Workspace

terraform apply              # WRONG — might hit prod
terraform workspace select dev && terraform apply   # correct

4. Drift Between Environments

Code is shared but states diverge — a prod hotfix that isn't in dev causes confusion.

Alternative: Directory Structure

For production use, many teams prefer separate directories instead of workspaces:

infra/
├── modules/
│   └── web-app/        # shared module
├── environments/
│   ├── dev/
│   │   ├── main.tf     # calls modules
│   │   └── backend.tf  # separate state backend
│   ├── staging/
│   └── prod/
│       ├── main.tf
│       └── backend.tf  # different subscription/storage
# environments/dev/backend.tf — separate state
terraform {
  backend "azurerm" {
    resource_group_name  = "tfstate-dev-rg"
    storage_account_name = "tfstatedev"
    container_name       = "tfstate"
    key                  = "dev.tfstate"
  }
}
# environments/prod/backend.tf — completely different state
terraform {
  backend "azurerm" {
    resource_group_name  = "tfstate-prod-rg"
    storage_account_name = "tfstateprod"
    container_name       = "tfstate"
    key                  = "prod.tfstate"
  }
}

When to Use Which

WorkspacesDirectory Structure
Quick dev/staging parityProduction isolation
Small team, simple setupMultiple environments
Same backend OKDifferent subscriptions/accounts
Learning TerraformEnterprise/production

Best Practices

1. Always Check Workspace Before Apply

# Safety check in CI/CD
CURRENT_WS=$(terraform workspace show)
if [ "$CURRENT_WS" != "dev" ]; then
  echo "Not in dev workspace! Current: $CURRENT_WS"
  exit 1
fi
terraform apply

2. Lock State Per Workspace

# In backend config, enable state locking
terraform {
  backend "azurerm" {
    # ...
    use_azuread_auth = true
  }
}

3. Different Subscriptions? Use Directories

# prod uses a different Azure subscription — needs different provider
provider "azurerm" {
  subscription_id = var.subscription_id   # different per environment
}

4. Name Resources by Workspace

locals {
  env = terraform.workspace
}

resource "azurerm_resource_group" "main" {
  name     = "myapp-${local.env}-rg"   # myapp-dev-rg, myapp-prod-rg
  location = "uksouth"
}

5. Don't Use default Workspace

terraform workspace new dev
terraform workspace select dev
terraform apply
# Don't work in "default" — it's ambiguous

Related Articles


Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Intermediate
Estimated Reading Time: 12 minutes