Docs / Automation & IaC / Use Terraform Workspaces for Multi-Environment Management

Use Terraform Workspaces for Multi-Environment Management

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 120 views · 4 min read

Terraform workspaces let you manage multiple instances of the same infrastructure configuration — such as development, staging, and production environments — without duplicating code. Each workspace maintains its own state file while sharing the same configuration. This guide covers using workspaces effectively for multi-environment infrastructure management.

Understanding Workspaces

  • Default workspace: Every Terraform project starts with a "default" workspace
  • Named workspaces: Create additional workspaces for different environments
  • Isolated state: Each workspace has its own state file — changes in one don't affect others
  • Shared code: All workspaces share the same Terraform configuration files

Creating and Switching Workspaces

# List workspaces
terraform workspace list

# Create new workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new production

# Switch between workspaces
terraform workspace select production

# Show current workspace
terraform workspace show

# Delete a workspace (must not be current)
terraform workspace select default
terraform workspace delete dev

Environment-Specific Configuration

# variables.tf
variable "environment_configs" {
  type = map(object({
    server_count = number
    server_type  = string
    enable_backups = bool
    domain_prefix  = string
  }))

  default = {
    dev = {
      server_count   = 1
      server_type    = "cx22"
      enable_backups = false
      domain_prefix  = "dev"
    }
    staging = {
      server_count   = 2
      server_type    = "cx32"
      enable_backups = true
      domain_prefix  = "staging"
    }
    production = {
      server_count   = 3
      server_type    = "cx42"
      enable_backups = true
      domain_prefix  = "www"
    }
  }
}

locals {
  env    = terraform.workspace
  config = var.environment_configs[local.env]

  common_labels = {
    environment = local.env
    managed_by  = "terraform"
    workspace   = terraform.workspace
  }
}

# main.tf — Configuration adapts to workspace
resource "hcloud_server" "web" {
  count = local.config.server_count

  name        = "${local.env}-web-${count.index + 1}"
  server_type = local.config.server_type
  image       = "ubuntu-24.04"
  location    = "nbg1"
  backups     = local.config.enable_backups

  labels = merge(local.common_labels, {
    role = "webserver"
    index = count.index
  })
}

resource "cloudflare_record" "web" {
  count = local.config.server_count

  zone_id = var.cloudflare_zone_id
  name    = "${local.config.domain_prefix}${count.index > 0 ? count.index + 1 : ""}"
  type    = "A"
  content = hcloud_server.web[count.index].ipv4_address
  proxied = local.env == "production"
}

# Database — only production gets a dedicated server
resource "hcloud_server" "database" {
  count = local.env == "production" ? 1 : 0

  name        = "${local.env}-db-1"
  server_type = "cx42"
  image       = "ubuntu-24.04"
  backups     = true
  labels      = merge(local.common_labels, { role = "database" })
}

output "web_ips" {
  value = hcloud_server.web[*].ipv4_address
}

output "environment" {
  value = local.env
}

Per-Workspace Variable Files

# Use .tfvars files per workspace
# environments/dev.tfvars
cloudflare_zone_id = "zone-id-for-dev"
ssh_key_ids        = ["12345"]
alert_email        = "dev-team@example.com"

# environments/production.tfvars
cloudflare_zone_id = "zone-id-for-prod"
ssh_key_ids        = ["12345", "67890"]
alert_email        = "ops@example.com"

# Apply with workspace-specific vars
terraform workspace select production
terraform apply -var-file="environments/$(terraform workspace show).tfvars"

# Or automate with a wrapper script
#!/bin/bash
# deploy.sh
WORKSPACE=$(terraform workspace show)
terraform apply -var-file="environments/${WORKSPACE}.tfvars" "$@"

Remote State with Workspaces

# Backend configuration — workspaces auto-create separate state paths
terraform {
  backend "s3" {
    bucket         = "myorg-terraform-state"
    key            = "infrastructure/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"

    # State files will be stored as:
    # env:/dev/infrastructure/terraform.tfstate
    # env:/staging/infrastructure/terraform.tfstate
    # env:/production/infrastructure/terraform.tfstate

    workspace_key_prefix = "env"
  }
}

CI/CD Integration

# .github/workflows/terraform.yml
name: Terraform Deploy
on:
  push:
    branches: [main, develop]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3

      - name: Determine workspace
        id: workspace
        run: |
          if [ "${{ github.ref }}" == "refs/heads/main" ]; then
            echo "workspace=production" >> $GITHUB_OUTPUT
          else
            echo "workspace=staging" >> $GITHUB_OUTPUT
          fi

      - name: Terraform Init
        run: terraform init

      - name: Select Workspace
        run: terraform workspace select ${{ steps.workspace.outputs.workspace }}

      - name: Terraform Plan
        run: terraform plan -var-file="environments/${{ steps.workspace.outputs.workspace }}.tfvars" -out=tfplan

      - name: Terraform Apply
        if: github.ref == 'refs/heads/main'
        run: terraform apply tfplan

When NOT to Use Workspaces

Workspaces are not always the right choice:

  • Completely different infrastructure: If dev and prod have fundamentally different architectures, use separate directories
  • Different providers or accounts: If environments run in different cloud accounts, separate configurations are cleaner
  • Team isolation: If different teams manage different environments, separate repos provide better access control

Best Practices

  • Never use the "default" workspace — create named workspaces for all environments
  • Use workspace-specific .tfvars files for environment-specific values
  • Add workspace guards for destructive operations: prevent_destroy = local.env == "production"
  • Document which workspace maps to which environment in your README
  • Use remote state with workspace-aware backends for team collaboration
  • Consider the alternative: For complex environments, separate directories per environment may be cleaner

Was this article helpful?