Skip to content

Terraform workspaces and environments: how to structure dev, staging, and production

"Workspace" means two different things in Terraform and OpenTofu, and mixing them up leads to the most common environment layout mistakes. Here is the difference and the layouts that hold up.

Two kinds of workspace

CLI workspacePlatform workspace
Created withtofu workspace new stagingThe platform's dashboard or API
What it isAnother state file for the same backend and configurationA configuration with its own state, variables, credentials, policy, and run queue
Separate credentialsNo, same backend and same shellYes
Separate access controlNoYes

CLI workspaces are handy for short-lived copies, such as a feature environment you tear down after testing. They are a poor boundary between staging and production, because both share the same backend credentials and one wrong tofu workspace select applies to the wrong environment.

Layouts that hold up

One directory per environment

modules/
  network/
  service/
environments/
  staging/
    main.tf      # module "network" { source = "../../modules/network" }
  production/
    main.tf

Each environment is its own root module and its own platform workspace. Differences between environments are visible in code review. The cost is some repetition, which shared modules keep small.

One configuration, many platform workspaces

If environments really are identical apart from inputs, point several platform workspaces at the same directory and set different variables on each. Keep the differences in variables, not in count = var.env == "production" ? 1 : 0 scattered through the code.

Split by layer as well as environment

Large environments get slow and risky as one state. Split by layer and rate of change: network, data, then services. A change to a service then can't plan a change to the VPC, and each plan stays fast.

Sharing values between workspaces

Lower layers expose outputs; higher layers read them. Read only what you need:

  • tfe_outputs reads another workspace's outputs and works with platforms that speak the Terraform Cloud API, including StackShip.
  • terraform_remote_state reads the full state, including every sensitive attribute. Use it only when you must.

Many workspaces, one repository

With several workspaces in one repository, start runs only for the workspaces a change touches. In StackShip each workspace has a working directory and optional trigger paths such as environments/production/** and modules/**, so a change to a shared module plans every environment that uses it, and a change to staging plans only staging.

Workspaces in StackShip

  • Workspaces are grouped into projects, such as Platform or Payments.
  • Each has its own state history, variables, credential profiles, policy attachments, OpenTofu version, and execution region.
  • Runs in a workspace go one at a time, so two applies never race.
  • By default no workspace can read another's state. Grant the Outputs consumer role to share only non-sensitive outputs.

Production and staging then differ in the places that matter: their cloud role, their variables, and which policies are hard mandatory. See the platform overview.

Get started

Run your first OpenTofu plan today.

Free for every organization. Sign up with email or Google.