THE DEVOPS.COMPANY
← All articles
Terraform3 min read

Terraform environments without state chaos

Workspaces feel convenient until someone applies to the wrong environment by accident. A structure that makes that mistake hard to make.

The DevOps Company

There's a specific moment every growing Terraform setup eventually hits: someone runs terraform apply, expecting staging, and it applies to production instead. Nobody did anything obviously wrong — the workspace was just selected incorrectly, or a variable file wasn't passed, or the wrong TF_WORKSPACE was set in a shell from an hour ago.

This isn't a training problem. It's a structural one, and it's fixable.

Why Terraform workspaces are the usual culprit

Workspaces let you reuse the same configuration across environments by switching a piece of implicit state (terraform workspace select production). That's exactly the problem: the difference between "safe" and "dangerous" is one command, run silently, with no diff shown before it takes effect.

Workspaces aren't wrong for every use case — they're reasonable for ephemeral, short-lived environments (a preview environment per pull request, for example). They're the wrong tool for the handful of environments — dev, staging, production — where a mistake is expensive.

A structure that makes the mistake harder

For environments that matter, we prefer:

  • One root Terraform configuration per environment, each with its own backend configuration pointing at a distinct state file.
  • Shared logic factored into modules, called by each environment's root config with explicit, reviewed variables.
  • No implicit environment switching — moving between environments means changing directories, not running a workspace command.
environments/
  production/
    main.tf        # calls modules/network, modules/compute
    backend.tf      # state: s3://.../production/terraform.tfstate
  staging/
    main.tf
    backend.tf

This looks like more files than a workspace-based setup. It is. That's the point — the extra structure is what makes "which environment am I about to change" a question answered by which directory you're in, not by remembering to check.

CI should plan on every pull request, apply only on merge

Regardless of workspace or directory structure, the highest-leverage safety net is process: run terraform plan in CI on every pull request touching infrastructure, post the plan as a comment for review, and only run apply after merge to the branch that maps to that environment. This catches most mistakes — including the ones a better folder structure doesn't — before they touch real infrastructure.

State chaos is usually a naming and backend problem too

Beyond workspaces, the other common source of chaos is inconsistent state file naming and backend configuration copied by hand between environments. Standardize the backend key naming convention (<environment>/<component>/terraform.tfstate) and enforce it through a shared backend module or Terragrunt-style wrapper — don't leave it to memory.

None of this is exotic. It's the difference between infrastructure code that's forgiving of a Friday-afternoon mistake and infrastructure code that isn't.