Environments with Terraform Workspaces

TerraformInfrastructureDevOpsEnvironments
Share on LinkedIn

I once ran terraform destroy in what I thought was the staging workspace. It was production. The workspace name in the prompt was easy to miss — (production) in parentheses between the resource count and the confirmation prompt. Twenty-three resources deleted before I hit Ctrl+C. Workspaces didn't cause the incident, but they didn't prevent it either.

Terraform workspaces create multiple state instances within a single backend configuration. terraform workspace select staging switches to a different state file; the same .tf files produce different infrastructure depending on variable values. They're lightweight environment isolation — useful for small projects, risky as a sole environment strategy for production systems.

How workspaces work

$ terraform workspace list
  default
* staging
  production

$ terraform workspace select production

The terraform.workspace variable returns the current workspace name:

locals {
  environment = terraform.workspace
  instance_type = {
    staging    = "t3.small"
    production = "t3.large"
  }
}

resource "aws_instance" "app" {
  instance_type = local.instance_type[local.environment]
  tags = { Environment = local.environment }
}

Same code, different resources per workspace.

Workspace-based variable files

terraform workspace select staging
terraform apply -var-file=staging.tfvars

terraform workspace select production
terraform apply -var-file=production.tfvars

Wrap in a script to prevent mismatched workspace/vars:

#!/bin/bash
ENV=$1
terraform workspace select "$ENV"
terraform apply -var-file="${ENV}.tfvars"

Workspaces vs separate directories

Factor Workspaces Separate directories
Code duplication None Some — shared modules
Environment divergence Awkward — conditionals Natural
Access control Shared backend Separate backends, different IAM
Blast radius One config change affects all Scoped to one environment
Best for Dev/staging identical shape Production with different sizing

Recommended hybrid approach

infra/
  modules/           # Shared reusable modules
  envs/
    staging/
      main.tf
      backend.tf
      staging.tfvars
    production/
      main.tf
      backend.tf
      production.tfvars

Both environments use the same modules. Root configs differ in variable values and possibly which modules are instantiated. State is fully isolated by backend key.

Safety guardrails

Prompt protection: Use lifecycle { prevent_destroy = true } on critical production resources.

Visual confirmation: Export PS1 to show current workspace: [tf:production].

CI/CD isolation: Separate GitHub environments with approval gates for production applies.

jobs:
  apply-production:
    environment: production
    steps:
      - run: terraform apply -var-file=production.tfvars -auto-approve
        working-directory: envs/production

Production applies require explicit approval. Staging applies run automatically on merge.

Naming and tagging conventions across workspaces

When workspaces share an AWS account, enforce naming: ${var.project}-${terraform.workspace}-${var.resource}. Tags should include Environment = terraform.workspace for cost allocation. Without convention, dev and staging resources become indistinguishable in Cost Explorer and during incident response.

When to migrate off workspaces

If environments diverge in resources, provider versions, or AWS accounts, migrate to directory-per-environment with separate backend keys. Import existing resources into new state with terraform state mv or declarative import blocks. Document the migration in an ADR so teams do not reintroduce workspace sprawl for production.

Operational checklist for teams

Assign an owner for each recurring failure mode. Measure baseline before changes and compare after two sprints. Document the fix in the team wiki or runbook so the next engineer does not rediscover it. Schedule a quarterly review of metrics, tooling, and process — what reduced flakes or improved quality last quarter should be doubled down on; what did not work should be retired explicitly rather than left on the books.

Operational checklist for teams

Assign an owner for each recurring failure mode. Measure baseline before changes and compare after two sprints. Document the fix in the team wiki or runbook so the next engineer does not rediscover it. Schedule a quarterly review of metrics, tooling, and process — what reduced flakes or improved quality last quarter should be doubled down on; what did not work should be retired explicitly rather than left on the books.

Operational checklist for teams

Assign an owner for each recurring failure mode. Measure baseline before changes and compare after two sprints. Document the fix in the team wiki or runbook so the next engineer does not rediscover it. Schedule a quarterly review of metrics, tooling, and process — what reduced flakes or improved quality last quarter should be doubled down on; what did not work should be retired explicitly rather than left on the books.

Common production mistakes

Teams get workspaces environments wrong in predictable ways:

Terraform patterns for workspaces environments rot when emergency console edits never get codified, ignore_changes blocks multiply without documentation, and drift detection runs monthly instead of daily on production workspaces.

Debugging and triage workflow

When workspaces environments misbehaves in production, work top-down instead of guessing:

  1. Confirm scope — one tenant, region, or deployment stage? Narrow blast radius before deep diving.
  2. Check recent changes — deploys, flag flips, config pushes, and schema migrations in the last 24 hours.
  3. Compare golden signals — latency, error rate, saturation, and traffic for the affected surface vs. baseline.
  4. Reproduce minimally — smallest input or scenario that triggers the failure; capture traces/logs with correlation IDs.
  5. Fix forward or rollback — if rollback is faster than root-cause during incident, rollback first, postmortem second.
  6. Add a guard — alert, integration test, or circuit breaker so the same class of failure is caught earlier next time.

Document the timeline during triage. Future you (and on-call) will need timestamps, not just conclusions.

Resources

Frequently asked questions

Should I use Terraform workspaces or separate directories for environments?

Separate directories (envs/staging/, envs/production/) when environments differ significantly — different instance sizes, feature flags, or resource counts. Workspaces when environments are nearly identical and differ only in variable values (CIDR ranges, domain names). Most teams outgrow workspaces quickly because staging and production inevitably diverge. Directories scale better for teams with different access controls per environment.

Do Terraform workspaces provide security isolation?

No. Workspaces share the same backend bucket and code. A terraform workspace select production followed by terraform destroy in the wrong terminal destroys production. Workspace isolation is state isolation, not access control. For production safety, use separate state files in separate backend paths with IAM policies restricting who can write to production state.

How do I pass different values per workspace?

Use .tfvars files named per workspace (staging.tfvars, production.tfvars) and pass them with terraform apply -var-file=staging.tfvars. Or use workspace-specific variable defaults with terraform.workspace in conditionals. Avoid hardcoding environment logic scattered across resources — centralize in locals or a variables file.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →