Skip to content

INFRA-0005: Resource Naming Convention — Region Suffix and Instance Numbering

Status

Accepted — amended 2026-06-17 (regionalize refactor): hisd removed from all names in the new Terraform roots (workload-core, workload-env, etc.) — it is a customer, not a component. Legacy live resources (the manually stood-up wa-eshars-* apps and associated rg-eshars-nonprod/rg-eshars-prod) keep their names until the rebuild cutover. Component clarified as the product part (web/api); full CAF environment tokens adopted (prod/staging/regression/training, not prd/stg/reg/trn); region driven by var.region, never folded into the tier. See Amendment section below. Amended again 2026-07 (as-built corrections): tier-RG component order is rg-eshars-{component}-{tier}-{region}, and per-env KVs are uniformly kv-eshars-{env}-{region} (tier vaults carry ts/rv infixes) — see the second Amendment section.

Date

2026-04-11 (amended 2026-06-17)

Context

Early infrastructure resources were named using {type}-{project}-{component}-{env} with -001 appended to some resource groups (e.g. rg-eshars-nonprod-data-001). Two problems emerged:

  1. -001 on resource groups is misleading. Resource groups don’t scale horizontally — there will never be a rg-eshars-nonprod-data-002. The suffix implies scaling that isn’t applicable and takes up space where useful information (region) could go.

  2. No region in names. All current infrastructure is in Central US, making region implicit. The product may be sold in Europe, which requires separate regional deployments for GDPR data residency. Without region in names, adding a second region later would create ambiguous or conflicting resource names.

Decision

Full naming convention

{type}-{project}-[{component}-]{env}-{region}

({component} is optional — omitted for shared platform/tier resources)

  • {project} = eshars (the product). Constant — aids global uniqueness for storage/KV and future-proofs for a second workload.
  • {component} = the part of the product: web (the .NET portal app + its function app) or api (the mobile API). OMITTED when the resource is a singleton at its scope — shared platform/tier resources (RGs, VNet, state SA, automation, Redis, tier Key Vault, App Service Plan) and per-environment singletons (SQL, App Insights, and most per-env Key Vaults). Exception: the prod-env Key Vault includes the component (kv-eshars-web-prod-cus) because kv-eshars-prod-cus would otherwise be ambiguous between the prod-tier KV and the prod-env KV. Non-prod per-env KVs (e.g. kv-eshars-regression-cus) have no collision since regression is only an env token, never a tier token. The customer/district (hisd) is never part of the name.
  • {env} = full environment token (see below). For shared-tier resources this is the tier (nonprod/prod); for per-environment resources it is the env (dev/qa/regression/staging/uat/training/prod).
  • {region} = short region code (cus), always last, from var.region.

Examples:

app-eshars-web-prod-cus # prod-env web app (component = web)
asp-eshars-nonprod-cus # shared nonprod-tier App Service Plan — component omitted (tier resource)
func-eshars-web-prod-cus # prod-env function app (component = web)
sql-eshars-regression-cus # per-env SQL — component omitted (singleton per env)
kv-eshars-regression-cus # per-env Key Vault (non-prod) — component omitted (no collision risk)
kv-eshars-prod-cus # prod-tier Key Vault — component omitted (tier resource)
kv-eshars-web-prod-cus # prod-env Key Vault — component INCLUDED (avoids collision with prod-tier KV above)
rg-eshars-nonprod-data-cus # shared nonprod-tier RG — component omitted
redis-eshars-nonprod-cus # shared nonprod-tier Redis — component omitted
law-eshars-nonprod-cus # shared nonprod-tier Log Analytics workspace
appi-eshars-prod-cus # prod-env App Insights

Region abbreviations

Azure RegionAbbreviation
Central UScus
North Europe (Ireland)neu
West Europe (Netherlands)weu
East USeus
East US 2eus2
West USwus

When to use -001 instance suffix

Append -001 only when the resource is a member of a scalable pool — i.e. you could reasonably have -002, -003 of the same resource serving the same role:

Use -001Don’t use -001
VMs (vm-eshars-sftp-prod-cus-001)Resource groups
Private endpoint subnetsApp Service Plans
SQL Server instances (shared across envs)Key Vaults
NIC / disks on scalable VMsWeb Apps (1 per env)

Rule of thumb: if the resource maps 1:1 to an environment or is logically singleton, no suffix. If it’s a member of a pool or could scale horizontally, add -001.

GDPR / multi-region implications

A European deployment requires:

  • Separate resource groups per region (e.g. rg-eshars-prod-data-neu)
  • Separate databases — EU customer data cannot be stored in US regions
  • DNS / Traffic Manager routing users to their regional endpoint
  • Shared codebase, separate IaC roots per region in OpenTofu

Amendment (2026-06-17) — regionalize refactor

The original record conflated a few things. Corrected:

Environment tokens (full CAF, no short codes)

TokenTierNotes
dev, qa, regression, stagingnonprodper-env
uat, training, prodprodper-env
nonprod, prodthe tier token, used for shared-tier infra

Short codes (prd/stg/reg/trn) are retired from new Terraform-managed infra. They may still appear in older docs and runbooks that predate this amendment and in the legacy live estate (the manually stood-up wa-eshars-* apps, rg-eshars-nonprod/rg-eshars-prod, and the pipelines/ + v1 runbooks that deploy to it) until that estate is cut over to the rebuild.

Tier vs. env — the two-level model

The subscription holds both tiers (nonprod + prod), each a shared platform serving multiple environments. So a name’s {env} segment carries one of two granularities:

  • tier (nonprod/prod) for resources shared across all envs in the tier (RGs, App Service Plan, Redis, tier Key Vault, automation account, state SA);
  • env (devprod) for per-environment resources (per-env Key Vault, SQL, slots).

Note prod denotes both the prod tier and the prod env. This is intentional: the prod tier has exactly one prod env. For most resource types the prefix distinguishes scope — asp-eshars-prod-cus is a tier resource (App Service Plan), app-eshars-web-prod-cus is a per-env resource (Web App). Exception: Key Vault uses kv- at both scopes (kv-eshars-prod-cus could be the tier KV or the per-env KV). Disambiguate by adding a component segment for the per-env KV: kv-eshars-web-prod-cus (per-env, component = web) vs kv-eshars-prod-cus (tier, no component).

Deviations from strict CAF (intentional, documented)

  • Constant eshars token — CAF’s {workload} segment is meant to discriminate; with one product it’s constant. Kept for global storage/KV uniqueness and future-proofing. Standard real-world practice.
  • nonprod as an env token for shared-tier infra — explicitly sanctioned by CAF (shared/nonprod are valid env values for cross-environment resources).
  • Region last, no -001 on singletons — see the instance-suffix rule above.

Key Vault / storage 24-char cap

With full tokens, the longest names at region=cus are kv-eshars-regression-cus (= 24, at the limit) and saesharsregressioncus (= 21) — both valid. A longer short region code (e.g. eus2 for East US 2) would push regression/training Key Vault names over 24 (kv-eshars-regression-eus2 = 25); handle that per-resource rather than degrading the global env token. The KV-only short form when needed: drop the hyphens and use kveshars{env}{region} (matches storage account style) — e.g. kvesharsregressioneus2 (= 22). Note: {region} is always the short code (e.g. cus, eus2, neu — see the Region abbreviations table) not the Azure location name (centralus, eastus2).

Global / non-region-bound resources omit the region (added 2026-06-18)

Some resources are not region-bound and must NOT carry a region token:

  • Global ARM types (no location at all): private & public DNS zones, role assignments / custom role definitions, Azure AD app registrations / service principals / federated credentials, Front Door, Traffic Manager, management groups, policy definitions.
  • A region suffix on these would be a lie — there is exactly one of them across all regions.

These live in a region-less, tier-scoped resource group: rg-eshars-global-{tier} (e.g. rg-eshars-global-nonprod). It’s named global rather than dns so it can hold any future global resource (public DNS, Front Door, Traffic Manager) without a rename. Azure dictates the zone names themselves (privatelink.blob.core.windows.net), so this convention applies only to the containing RG and our own resource names — not the zone names.

The global private DNS zones live in their own Terraform root, control-plane-dns-zones — separate from control-plane-core, which is the regional hub (one per region). The hub’s VNet/subnets/NSGs/VPN-gateway are all regional and DO carry the region (vnet-eshars-core-nonprod-cus). Consumers resolve the global zones via a data "azurerm_private_dns_zone" lookup keyed on a global_rg variable (= rg-eshars-global-{tier}).

Globally-unique-name ≠ region-less. Key Vault, storage accounts, and App Service have globally-unique names but we deliberately KEEP the region in them (kv-eshars-prod-cus, sattfprodcus, app-eshars-web-prod-cus). There, the region disambiguates our own per-region instances — it’s meaningful, not a lie. The omit-region rule applies only to the truly global ARM types listed above.

Amendment (2026-07) — as-built corrections

Two places where the shipped Terraform estate deliberately deviates from the examples earlier on this page; the estate is the source of truth:

  1. Tier RGs put the component before the tier: rg-eshars-{component}-{tier}-{region} — e.g. rg-eshars-data-nonprod-cus, not rg-eshars-nonprod-data-cus as some earlier examples read. This keeps the RG pattern consistent with {type}-{project}-[{component}-]{env}-{region} (the component slot comes right after the project). Eight components: security, data, storage, web, monitoring, compute, ops, backup.
  2. The prod-env Key Vault is kv-eshars-prod-cus (no web component). The collision this page worried about was resolved differently: the tier-shared vault carries a ts infix (kv-eshars-ts-{tier}-{region}), and the ReportViewer tier vault an rv infix (kv-eshars-rv-{tier}-{region}), so every per-env vault is uniformly kv-eshars-{env}-{region}.

Consequences

Positive

  • Resource names are unambiguous when multiple regions exist
  • No misleading -001 on singleton resources
  • Future EU deployment can follow the same convention without renaming

Negative

  • Existing resources named under the old convention must be renamed — resource groups require create-new + move-resources (some resource types cannot be moved and require recreate)
  • Migration is ongoing — not all existing resources have been renamed yet

Migration Status (as of 2026-04-11)

Old NameNew NameStatus
rg-eshars-nonprod-data-001rg-eshars-nonprod-data-cus✅ Done — resources moved
rg-eshars-nonprod-security-001rg-eshars-nonprod-security-cus⬜ Pending
rg-eshars-nonprod-web-001rg-eshars-nonprod-web-cus✅ Already correct
rg-eshars-nonprod-centralUSrg-eshars-nonprod-cus⬜ Pending
All prod RGsAdd -cus suffix⬜ Pending — lower priority