Skip to main content

Environments & Deployment

There is one copy of the code and five environments — hub, sbx, dev, uat, prd — and no environment has its own fork. That's deliberate: five copies would drift. Instead the same stack is deployed five ways, and the mechanism is worth understanding precisely, because it's also what keeps a dev change from ever touching production by accident.

The one idea to hold on to​

An environment is a (workspace, tfvars, subscription) triple applied to the same code. The workspace isolates that environment's state; the envs/<env>.tfvars file supplies its overrides; the subscription is the target it deploys into. Change the triple, deploy the same code, get a different environment.

Workspaces isolate state​

All five environments share one remote state backend — an azurerm backend in the HUB subscription (a storage account, azw2hubterraformstates, container alertca). If they all wrote to the same state, they'd overwrite each other. Terraform workspaces prevent that: each workspace keeps its own independent state under that one backend, so dev and prd can be planned and applied without ever seeing each other's resources.

# terraform.tf — one backend for all environments
backend "azurerm" {
resource_group_name = "azw2-inf-rg-terraform"
storage_account_name = "azw2hubterraformstates"
container_name = "alertca"
key = "terraform.tfstate"
use_azuread_auth = true
}

Selecting a workspace (terraform workspace select prd) switches which state Terraform reads and writes. This selection is the single most consequential command in a deploy — it decides which environment you're about to change.

Per-environment overrides live in tfvars​

The code holds sensible, non-sensitive defaults in variables.tf. What actually differs between environments — SKUs, capacities, feature toggles, environment-specific names — lives in envs/<env>.tfvars, passed to every plan and apply with -var-file:

terraform plan -var-file="./envs/prd.tfvars" -out=tfplan_prd

The discipline (from the repo's own conventions): keep a value in variables.tf unless an environment genuinely needs to override it — only then does it earn a place in the tfvars files. This keeps the environment files small and their diffs meaningful.

The deployment pipeline: plan, approve, apply the saved plan​

Deployment is one Azure DevOps pipeline (terraform-apply.yml), parameterized by environment and an optional versionRef. Its safety comes from separating plan from apply and applying the exact plan that was reviewed:

Reading it step by step:

  1. Init & select — Terraform initializes the backend and runs terraform workspace select <env> so all following commands target that environment's state.
  2. Plan — terraform plan -var-file=envs/<env>.tfvars -out=tfplan_<env> computes the diff and saves it as an artifact. The pipeline then inspects the plan and, if it reads "No changes. Your infrastructure matches the configuration," it skips apply entirely.
  3. Approve — the apply runs as a deployment job targeting the DevOps environment terraform-<env>, which is where approval checks live (production requires sign-off).
  4. Apply the saved plan — apply consumes the stored tfplan artifact, not a fresh plan. Whatever a reviewer approved is exactly what runs — there's no window for reality to shift between review and apply.
Gotcha: apply runs the reviewed plan, not the current config

Because the apply job applies the saved plan artifact, the thing that executes is the diff that was reviewed, not a re-plan at apply time. That's the safety property — but it means a stale plan can be applied if a lot of time passes or someone changes state underneath. Treat a plan artifact as perishable: review and apply close together, and re-run the plan if the world may have moved.

Promoting a change across environments​

Because the pipeline accepts a versionRef (a git tag or commit SHA) and checks out that exact revision, promotion is reproducible: deploy the same versionRef to dev, verify, then run it again for uat, then prd. Each run is the identical code, differing only by workspace and tfvars — so "it worked in UAT" means the very same artifact is what reaches production.

Gotcha: the workspace, not the folder, decides the target

There is no prd/ directory to be "in." The environment is whatever workspace is selected, so a plan run in the dev workspace with prd.tfvars is a nonsensical mix that targets dev's state with prod's values. The pipeline pairs them correctly; if you ever run Terraform locally, select the workspace and pass the matching tfvars together, and double-check terraform workspace show before you plan.

Further reading​

Wrapping up​

One stack becomes five environments through a (workspace, tfvars, subscription) triple: workspaces isolate each environment's state within one remote backend, tfvars carry the overrides, and a plan-then-apply pipeline reviews a saved plan behind an approval gate before applying exactly that. Promotion is re-running the same pinned version against the next environment. The workspace selection is the load-bearing choice — respect it.

Now that you can read the stack and know how it ships, the next lesson makes changing it concrete: Adding a Component.