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:
-
-001on resource groups is misleading. Resource groups don’t scale horizontally — there will never be arg-eshars-nonprod-data-002. The suffix implies scaling that isn’t applicable and takes up space where useful information (region) could go. -
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) orapi(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) becausekv-eshars-prod-cuswould 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 sinceregressionis 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, fromvar.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 omittedredis-eshars-nonprod-cus # shared nonprod-tier Redis — component omittedlaw-eshars-nonprod-cus # shared nonprod-tier Log Analytics workspaceappi-eshars-prod-cus # prod-env App InsightsRegion abbreviations
| Azure Region | Abbreviation |
|---|---|
| Central US | cus |
| North Europe (Ireland) | neu |
| West Europe (Netherlands) | weu |
| East US | eus |
| East US 2 | eus2 |
| West US | wus |
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 -001 | Don’t use -001 |
|---|---|
VMs (vm-eshars-sftp-prod-cus-001) | Resource groups |
| Private endpoint subnets | App Service Plans |
| SQL Server instances (shared across envs) | Key Vaults |
| NIC / disks on scalable VMs | Web 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)
| Token | Tier | Notes |
|---|---|---|
dev, qa, regression, staging | nonprod | per-env |
uat, training, prod | prod | per-env |
nonprod, prod | — | the 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 (
dev…prod) 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
esharstoken — 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. nonprodas an env token for shared-tier infra — explicitly sanctioned by CAF (shared/nonprodare valid env values for cross-environment resources).- Region last, no
-001on 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
locationat 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:
- Tier RGs put the component before the tier:
rg-eshars-{component}-{tier}-{region}— e.g.rg-eshars-data-nonprod-cus, notrg-eshars-nonprod-data-cusas 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. - The prod-env Key Vault is
kv-eshars-prod-cus(nowebcomponent). The collision this page worried about was resolved differently: the tier-shared vault carries atsinfix (kv-eshars-ts-{tier}-{region}), and the ReportViewer tier vault anrvinfix (kv-eshars-rv-{tier}-{region}), so every per-env vault is uniformlykv-eshars-{env}-{region}.
Consequences
Positive
- Resource names are unambiguous when multiple regions exist
- No misleading
-001on 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 Name | New Name | Status |
|---|---|---|
rg-eshars-nonprod-data-001 | rg-eshars-nonprod-data-cus | ✅ Done — resources moved |
rg-eshars-nonprod-security-001 | rg-eshars-nonprod-security-cus | ⬜ Pending |
rg-eshars-nonprod-web-001 | rg-eshars-nonprod-web-cus | ✅ Already correct |
rg-eshars-nonprod-centralUS | rg-eshars-nonprod-cus | ⬜ Pending |
| All prod RGs | Add -cus suffix | ⬜ Pending — lower priority |