Azure Resource Tagging Standards
Azure Resource Tags
The Azure resources provisioned through Terraform must have the below set of tags on them. Tags are managed centrally via the ohemr-epic-private-registry-azurerm module (HCP v1.8 compliant).
Note: Variable names use underscores (e.g.,
aide_id) but map to hyphenated tag keys (e.g.,aide-id) for HCP compliance. Validation rules below reflect the private registry module (tagging-variables.tf).
HCP v1.8 Required Tags
These tags are mandatory on all resources when enable_standardized_tagging = true.
HCP v1.8 Recommended Tags
Optional but recommended for full compliance.
<table> <tr> <th>Tag Name</th> <th>Description</th> <th>Allowed Values</th> <th>Validation</th> </tr> <tr> <td>itsm-assignment-group</td> <td>ServiceNow assignment group</td> <td>EPIC NATIONAL INSTANCE - SPT, USS_Virtual_Workspace</td> <td>Default: EPIC NATIONAL INSTANCE - SPT</td> </tr> <tr> <td> risk-record (optional)</td> <td> new tag - Set of risk records applicable on a resource that actually have risk records should have this tag </td> <td>Comma separated risk record #</td> <td>None</td> </tr> <tr> <td>source-code-repo</td> <td>Identifies the repo where the resource was deployed</td> <td>Link to the source code repo</td> <td>None</td> </tr> <tr> <td>dr-tier</td> <td>DR option for the server</td> <td>active, standby, restoration</td> <td>Enum: active, standby, restoration</td> </tr> </table>Organizational Tags
<table> <tr> <th>Tag Name</th> <th>Description</th> <th>Allowed Values</th> <th>Validation</th> </tr> <tr> <td>component</td> <td>High level VM / resource identifier</td> <td>Citrix Netscaler, Epic ODB, Epic ECSA, Cogito, Infoblox, WSUS, Core Services</td> <td>None</td> </tr> <tr> <td>gl-code</td> <td>General ledger code for financial tracking. Auto-resolved by workload x environment in the module.</td> <td>44770-01530-USASS800-XXXXXX (last 6 digits vary, see GL Code Reference below)</td> <td>Auto-mapped from workload + environment</td> </tr> <tr> <td>managed-by</td> <td>Management tool used for this resource</td> <td>terraform</td> <td>Default: terraform</td> </tr> </table>Epic Operational Tags
<table> <tr> <th>Tag Name</th> <th>Description</th> <th>Allowed Values</th> <th>Validation</th> </tr> <tr> <td>workload</td> <td>Workload type for this deployment (also drives GL code mapping)</td> <td>epic, citrix, connectivity, network, storage, monitoring, security, shared</td> <td>Enum: 8 allowed values</td> </tr> <tr> <td>region</td> <td>Azure region for deployment</td> <td>centralus, eastus, westus3, etc.</td> <td>None</td> </tr> <tr> <td>data-classification</td> <td>Data classification level (module-level operational tag, distinct from DataClassification Azure Policy tag)</td> <td>public, internal, confidential, restricted</td> <td>Enum: 4 allowed values</td> </tr> <tr> <td>backup-required</td> <td>Whether backup is required for this resource</td> <td>true, false</td> <td>Enum: "true" or "false"</td> </tr> </table>VM Extension Tags (from tfvars)
These 6 tags are set per-VM via terraform.tfvars.json, in addition to the 16 standard tags above.
Tag Limits
| Limit | Value | Notes |
|---|---|---|
| Azure standard limit | 50 | Maximum tags per resource in Azure |
| HCP v1.8 enforced limit | 15 | For limited Azure services (Automation, CDN, DNS, Log Analytics) |
Module default (azure_tag_limit) | 25 | Allows room for deployment-level tags. Configurable: 15-50. |
| Core tags set | 14 | Subset excluding component and region for tag-limited services |
| Custom tags max | 40 | platform-* prefix reserved for platform teams |
GL Code Reference
GL codes are automatically resolved by the private registry module based on workload x environment. Format: 44770-01530-USASS800-XXXXXX.
| Workload | Dev | Test | Stage | Prod | Shared | Training | Demo |
|---|---|---|---|---|---|---|---|
| epic | 169950 | 169951 | 169952 | 169953 | 169954 | 169955 | 169956 |
| citrix | 169960 | 169961 | 169962 | 169963 | 169964 | 169965 | 169966 |
| connectivity | 169970 | 169971 | 169972 | 169973 | 169974 | 169975 | 169976 |
| network | 169980 | 169981 | 169982 | 169983 | 169984 | 169985 | 169986 |
| storage | 169990 | 169991 | 169992 | 169993 | 169994 | 169995 | 169996 |
| monitoring | 170000 | 170001 | 170002 | 170003 | 170004 | 170005 | 170006 |
| security | 170010 | 170011 | 170012 | 170013 | 170014 | 170015 | 170016 |
| shared | 170020 | 170021 | 170022 | 170023 | 170024 | 170025 | 170026 |
Note:
npdmaps to the same GL code asdev.cloudtestmaps to the same astest.
Module Tag Outputs
The ohemr-epic-private-registry-azurerm module provides these tag output sets:
| Output | Description | Use Case |
|---|---|---|
standardized_tags | Complete set of all tags | General resources |
business_tags | aide-id, gl-code, itsm-assignment-group, workload | Cost allocation / reporting |
technical_tags | environment, service-tier, managed-by, data-classification, backup-required, dr-tier, etc. | Operations / automation |
hcp_tags | HCP required + recommended tags | HCP compliance only |
core_tags | 14-tag subset (excludes component, region) | Tag-limited Azure services |
tag_validation | Tag counts, limits, remaining capacity | Validation / debugging |
Sample tagging.tf Implementation
Every workspace that uses the terraform-deploy pattern must include a tagging.tf file. This file centralizes all 16 standard tags in one place and feeds them into the deploy module via local.enhanced_inputs.
Structure
The file has four sections:
locals.workspace_config— All tag values for this workspace in one block.module "standardized_tagging"— Calls the private registry module to produce validated, HCP-compliant tag maps.locals.enhanced_inputs— Merges the standardized tags intovar.inputsso the deploy module receives them automatically.- Outputs — Exposes
standardized_tags,business_tags,technical_tags,core_tags, andtag_validationfor debugging and downstream use.
Complete Example
# ==============================================================================
# Standardized Tagging Module Configuration
# ==============================================================================
# Workspace: westepicnpd/cogitowestus3
# TFE Workspace: aide-0085665-tfews-epic-cogito-westepic-npd-wus3-01
# Region: westus3
# ==============================================================================
locals {
workspace_config = {
# ---- HCP v1.8 Required Tags (lowercase) ----
aide_id = "aide_0085665"
environment = "dev"
service_tier = "p3"
platform_managed = "true"
workspace = "aide-0085665-tfews-epic-cogito-westepic-npd-wus3-01"
# ---- HCP Recommended Tags ----
itsm_assignment_group = "EPIC NATIONAL INSTANCE - SPT"
source_code_repo = "ohemr-epic-npd-001/westepicnpd/cogitowestus3"
dr_tier = "restoration"
risk_record = ""
# ---- Organizational + Operational Tags ----
workload = "epic"
component = "Cogito"
region = "westus3"
data_classification = "internal"
backup_required = "true"
managed_by = "terraform"
# ---- Custom Tags ----
custom_tags = {
"working-directory" = "westepicnpd/cogitowestus3"
}
}
}
# ==============================================================================
# Standardized Tagging Module
# ==============================================================================
module "standardized_tagging" {
source = "terraform.uhg.com/uhg-customer-modules/private-registry-azurerm/epic"
version = "~> 6.0.0"
enable_standardized_tagging = true
azure_tag_limit = 30
# HCP v1.8 Required
aide_id = local.workspace_config.aide_id
environment = local.workspace_config.environment
service_tier = local.workspace_config.service_tier
platform_managed = local.workspace_config.platform_managed
workspace = local.workspace_config.workspace
# HCP Recommended
itsm_assignment_group = local.workspace_config.itsm_assignment_group
source_code_repo = local.workspace_config.source_code_repo
dr_tier = local.workspace_config.dr_tier
risk_record = local.workspace_config.risk_record
# Organizational + Operational
workload = local.workspace_config.workload
component = local.workspace_config.component
region = local.workspace_config.region
data_classification = local.workspace_config.data_classification
backup_required = local.workspace_config.backup_required
managed_by = local.workspace_config.managed_by
# Custom
custom_tags = local.workspace_config.custom_tags
}
# ==============================================================================
# Enhanced Inputs — Merge standardized tags into deploy module inputs
# ==============================================================================
locals {
enhanced_inputs = merge(
var.inputs,
{
tags = merge(lookup(var.inputs, "tags", {}), module.standardized_tagging.standardized_tags)
standardized_tags = module.standardized_tagging.standardized_tags
business_tags = module.standardized_tagging.business_tags
technical_tags = module.standardized_tagging.technical_tags
core_tags = module.standardized_tagging.core_tags
}
)
}
# ==============================================================================
# Outputs
# ==============================================================================
output "standardized_tags" {
description = "Complete set of standardized tags applied to resources"
value = module.standardized_tagging.standardized_tags
}
output "tag_validation" {
description = "Tag validation and count information"
value = module.standardized_tagging.tag_validation
}
output "business_tags" {
description = "Business-focused tags for cost allocation"
value = module.standardized_tagging.business_tags
}
output "technical_tags" {
description = "Technical-focused tags for operations"
value = module.standardized_tagging.technical_tags
}
output "core_tags" {
description = "Core tags for 5 special resources in Azure"
value = module.standardized_tagging.core_tags
}
Integration Step
After adding tagging.tf, update acn-main.tf to use the enhanced inputs:
# BEFORE
module "deploy" {
source = "../terraform-deploy"
inputs = var.inputs
}
# AFTER
module "deploy" {
source = "../terraform-deploy"
inputs = local.enhanced_inputs
}
This ensures all resources created by the deploy module automatically receive the 16 standard tags without any per-resource configuration.
Cleaning Up terraform.tfvars.json
Once tagging.tf is in place, the 16 standard tags are managed centrally by the module. Any standard tags still present in terraform.tfvars.json are redundant and must be removed to avoid conflicts and drift.
What to Remove
Top-level inputs.tags block
This block typically contains standard tags that are now handled by the module. Remove the entire inputs.tags block (or reduce it to "tags": {}).
Before (old pattern — tags duplicated in tfvars):
{
"inputs": {
"tags": {
"aide-id": "aide_0085665",
"itsm-assignment-group": "EPIC NATIONAL INSTANCE - SPT",
"data-classification": "NONPHI",
"Division": "Optum Health",
"dr-tier": "active",
"environment": "tst",
"gl-code": "44770-01530-USASS800-169950",
"Portfolio": ".",
"service-tier": "p3",
"risk-record": "rr0000",
"Product": "Epic EMR",
"workspace-id": "aide-0085665-tfews-epic-hyperspace-westepic-npd-wus3-01",
"working-directory": "westepicnpd/hyperspacewestus3",
"platform-managed": "true",
"source-code-repo": "https://github.com/optum-tech-compute/ohemr-epic-npd-001"
}
}
}
After (standard tags removed — module handles them):
{
"inputs": {
"tags": {}
}
}
Deployment-level tags blocks
Keep only the 6 VM extension tags. Remove any standard tags that were duplicated at the deployment level.
Before (old pattern — mixed standard + VM extension tags):
"deployments": {
"HyperspaceWeb": {
"tags": {
"SolutionName": "Epic App",
"Env": "NON PROD TRAINING",
"epic-app": "hyperspace",
"epic-stamp": "training",
"Cost Center": ".",
"TechOwner": "[email protected]",
"AccountOwner": "[email protected]",
"PatchSchedule": "ZWW0D1H02",
"Component": "Epic ECSA",
"ComponentVersion": "1.0"
}
}
}
After (only 6 VM extension tags remain, with normalized key names):
"deployments": {
"HyperspaceWeb": {
"tags": {
"solution-name": "Epic App",
"epic-app": "hyperspace",
"epic-stamp": "training",
"tech-owner": "[email protected]",
"account-owner": "[email protected]",
"PatchSchedule": "ZWW0D1H02"
}
}
}
Tags removed: Env, Cost Center, Component, ComponentVersion (all now covered by the module or obsolete).
Tag Key Normalization Rules
All tag keys in terraform.tfvars.json must follow the lowercase-hyphenated convention, except PatchSchedule which is preserved as-is (external system requirement).
| Old Key (remove or rename) | New Key | Action |
|---|---|---|
SolutionName | solution-name | Rename |
TechOwner | tech-owner | Rename |
AccountOwner | account-owner | Rename |
Component | Remove | Covered by tagging.tf |
ComponentVersion | Remove | Covered by tagging.tf |
DR | Remove | Covered by tagging.tf (dr-tier) |
Env | Remove | Covered by tagging.tf (environment) |
Division | Remove | Covered by tagging.tf |
Product | Remove | Covered by tagging.tf |
Portfolio | Remove | Obsolete |
Cost Center | Remove | Obsolete |
DataClassification | Remove | Covered by tagging.tf (data-classification) |
workspace-id | Remove | Covered by tagging.tf (workspace) |
working-directory | Remove from top-level tags | Moved to custom_tags in tagging.tf |
AIDE_0085665 (uppercase value) | aide_0085665 | Lowercase the value (key aide-id stays) |
PatchSchedule | PatchSchedule | Keep as-is — exception |
Normalization Pattern Summary
| Pattern | Example | Normalized |
|---|---|---|
| PascalCase | SolutionName | solution-name |
| camelCase | techOwner | tech-owner |
| ALLCAPS | ENV | env |
| Space Separated | Cost Center | cost-center |
| Underscore | component_version | component-version |
| Already Correct | epic-app | epic-app (no change) |
| Exception | PatchSchedule | PatchSchedule (preserved) |
Checklist Per Workspace
- Add
tagging.tfwith the 16 standard tag values for this workspace. - Update
acn-main.tfto uselocal.enhanced_inputsinstead ofvar.inputs. - Remove all standard tags from
inputs.tagsinterraform.tfvars.json. - In each deployment's
tagsblock, keep only the 6 VM extension tags. - Normalize remaining tag keys to lowercase-hyphenated (except
PatchSchedule). - Run
terraform planto verify no tag changes are introduced.