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:
- Init & select — Terraform initializes the backend and runs
terraform workspace select <env>so all following commands target that environment's state. - 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. - Approve — the apply runs as a deployment job targeting the DevOps environment
terraform-<env>, which is where approval checks live (production requires sign-off). - Apply the saved plan — apply consumes the stored
tfplanartifact, not a fresh plan. Whatever a reviewer approved is exactly what runs — there's no window for reality to shift between review and apply.
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.
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.