TT Lab
Get started
Learn Learning paths Courses

Terraform in Practice

Workspaces Are Not an Environment-Separation Tool

Continue in TT Lab

In one sentence

A workspace is a device that gives you multiple copies of only the state within one backend and one set of credentials. If the point where your environments split is permissions, you cannot divide them with workspaces.

Why this was needed — and why it is misunderstood

Splitting environments almost always starts like this. You have to bring the stack built in dev up in prod too, but you do not want to copy the code wholesale. At this point workspace new prod looks like a perfect answer. There is one copy of the code, you switch with one line of command, and you do not even re-download the provider cache.

The problem lies in what this tool solves. What a workspace splits is only the state. The backend stays one, the credentials that open that backend are one, and the code is one. So if you make someone who should be given only permission to touch dev use workspaces, that person already holds the key that opens the same store containing the prod state.

The official documentation says the same. OpenTofu's Workspaces documentation states that workspaces are "not appropriate for system decomposition or deployments requiring separate credentials and access controls," and for larger systems recommends splitting the configuration itself along architectural boundaries. In other words, a workspace is not an environment-separation tool but a tool for making a temporary copy of the same environment. The typical example the documentation gives is a temporary clone for a feature branch.

How it works

What actually happens on the local backend is all told by the file layout.

tfa-ws/
├── terraform.tfstate                      ← default workspace
├── terraform.tfstate.d/
│   ├── dev/terraform.tfstate
│   └── prod/terraform.tfstate
└── .terraform/
    ├── providers/                          ← 캐시는 한 벌뿐이다
    └── environment                         ← 지금 선택된 이름이 여기에만 있다

The two Korean comments in this layout say that there is only one copy of the cache under providers, and that the currently selected name exists only in the environment file.

There are three things to read.

Directory separation has exactly the opposite properties. With a separate directory per environment, the state is separate, init is separate, and the provider cache is separate. As the documentation admits, it uses more disk and bandwidth and you must update the configuration in each one. In exchange, you can give the backend configuration differently for each environment, and through repository permissions you can turn "who can change the prod directory" into a code review rule.

What it looks like in the field

The most common incident is not syntax but selection. The terminal has no prod written anywhere, it does not show in the prompt, and it is not in the command. If you type destroy this morning in the directory where you were looking at prod yesterday and left it as it was when you went home, that is it. In step 5 of the lab, you cause this incident on purpose.

The second is a team that split by workspace although permissions were already divided. When an audit asks "can the dev owner read the prod state?", the answer is always yes. It is because there is one backend. This finding cannot be fixed and blocked by modifying the code, and becomes a migration task of moving to directories (or separate configurations).

The third is workspace names leaking into conditionals in the code. When terraform.workspace == "prod" ? ... : ... goes past three or four places, that code is already two different codebases per environment. That is when it is time to split into directories.

The way to prevent it is simple. Put a one-line guard in front of write commands so that it stops unless the expected workspace is selected. In CI, receive the environment name as a pipeline variable and run the same check. You make the exit code judge instead of human memory. Splitting exit codes into three rather than two is also worthwhile in practice — because the pipeline must distinguish "different from expected" from "the script was called wrongly" to decide whether to retry or call a person.

To summarize, the selection criterion becomes this. Is it enough to split only the state, or must you split credentials, backends, and even review permissions? If the former, a workspace is the cheapest answer, and if the latter, a workspace is not the answer. Mixing the two is also common — splitting prod and non-production broadly into directories, and within non-production keeping per-developer temporary copies as workspaces.

What to do in the next lab

In /root/tfa-ws you create three workspaces and open up where the state files are created, give different values per environment with a map, cause an incident by running destroy with prod selected, and then recover. Next you rebuild the same result with directory separation, count the number of state files and provider caches, and compare the two approaches in a table.