Navigation
Best PracticesUpdated July 3, 2026

Azure Resource Tagging Standards

referenceazuretaggingstandardsterraformresource-managementaide-idcost-trackingcompliance
<!-- prettier-ignore-start --> <!-- markdownlint-disable MD033 --> <!-- markdownlint-disable MD013 -->

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.

<table> <tr> <th>Tag Name</th> <th>Description</th> <th>Allowed Values</th> <th>Validation</th> </tr> <tr> <td>aide-id</td> <td>AIDE ID for resource ownership tracking</td> <td>aide_0077877 (PaloAlto), uhgwm110-017397 (Infoblox), aide_0073730 (Imprivata), aide_0085665 (Epic West), aide_0085666 (Epic East), aide_0088164 (Netscaler)</td> <td>Regex: <code>^(aide_\d+|uhgwm_[a-z]+)$</code></td> </tr> <tr> <td>environment</td> <td>Identifies if environment is Prod/Non-Prod/Test</td> <td>dev, qa, int, stg, tst, prf, uat, dmo, prd</td> <td>Must start with one of the allowed prefixes</td> </tr> <tr> <td>service-tier</td> <td>Service level classification</td> <td>p1, p2, p3</td> <td>Enum: p1, p2, p3</td> </tr> <tr> <td>platform-managed</td> <td>Indicates if resource is managed by a platform team</td> <td>true, false</td> <td>Enum: "true" or "false"</td> </tr> <tr> <td>workspace</td> <td>Workspace identifier for the deployment (e.g., odbwus3, testlogworkspace-wus3)</td> <td>Lowercase alphanumeric with hyphens</td> <td>Regex: <code>^[a-z0-9][a-z0-9-]*[a-z0-9]$</code></td> </tr> </table>

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.

<table> <tr> <th>Tag Name</th> <th>Description</th> <th>Allowed Values</th> </tr> <tr> <td>solution-name</td> <td>Application or Solution name</td> <td>epic-app, epic-stamp, epic-cogito, epic-shared-infrastructure</td> </tr> <tr> <td>epic-app</td> <td>Name of app installed on the server/resource</td> <td>arr, bca, bcaweb, birestful, careeverywhere, careeverywherearr, dss, ecl, hsw, hyperspace, hyperdrive, interconnect, systempulse, wbs, eps, kuiper, mychart, sts, welcomeweb, sharedinfra</td> </tr> <tr> <td>epic-stamp</td> <td>Defined stamp from CSG</td> <td>production, alternate-production, build, training, read-only, shared-infrastructure</td> </tr> <tr> <td>tech-owner</td> <td>Technical owner email</td> <td>Email address</td> </tr> <tr> <td>account-owner</td> <td>Business owner email</td> <td>Email address</td> </tr> <tr> <td>PatchSchedule</td> <td>Used to identify patch schedule and patch exceptions. Enforced by Azure Policy with 50+ allowed values.</td> <td>Examples: ALL_DO_NOT_PATCH, ZWW0D6H02, ZEPCDWS102AM, ZCTXDWS102AM. Full list enforced in ohemr-epic-azure-policy.</td> </tr> </table>

Tag Limits

LimitValueNotes
Azure standard limit50Maximum tags per resource in Azure
HCP v1.8 enforced limit15For limited Azure services (Automation, CDN, DNS, Log Analytics)
Module default (azure_tag_limit)25Allows room for deployment-level tags. Configurable: 15-50.
Core tags set14Subset excluding component and region for tag-limited services
Custom tags max40platform-* 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.

WorkloadDevTestStageProdSharedTrainingDemo
epic169950169951169952169953169954169955169956
citrix169960169961169962169963169964169965169966
connectivity169970169971169972169973169974169975169976
network169980169981169982169983169984169985169986
storage169990169991169992169993169994169995169996
monitoring170000170001170002170003170004170005170006
security170010170011170012170013170014170015170016
shared170020170021170022170023170024170025170026

Note: npd maps to the same GL code as dev. cloudtest maps to the same as test.

Module Tag Outputs

The ohemr-epic-private-registry-azurerm module provides these tag output sets:

OutputDescriptionUse Case
standardized_tagsComplete set of all tagsGeneral resources
business_tagsaide-id, gl-code, itsm-assignment-group, workloadCost allocation / reporting
technical_tagsenvironment, service-tier, managed-by, data-classification, backup-required, dr-tier, etc.Operations / automation
hcp_tagsHCP required + recommended tagsHCP compliance only
core_tags14-tag subset (excludes component, region)Tag-limited Azure services
tag_validationTag counts, limits, remaining capacityValidation / 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:

  1. locals.workspace_config — All tag values for this workspace in one block.
  2. module "standardized_tagging" — Calls the private registry module to produce validated, HCP-compliant tag maps.
  3. locals.enhanced_inputs — Merges the standardized tags into var.inputs so the deploy module receives them automatically.
  4. Outputs — Exposes standardized_tags, business_tags, technical_tags, core_tags, and tag_validation for 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 KeyAction
SolutionNamesolution-nameRename
TechOwnertech-ownerRename
AccountOwneraccount-ownerRename
ComponentRemoveCovered by tagging.tf
ComponentVersionRemoveCovered by tagging.tf
DRRemoveCovered by tagging.tf (dr-tier)
EnvRemoveCovered by tagging.tf (environment)
DivisionRemoveCovered by tagging.tf
ProductRemoveCovered by tagging.tf
PortfolioRemoveObsolete
Cost CenterRemoveObsolete
DataClassificationRemoveCovered by tagging.tf (data-classification)
workspace-idRemoveCovered by tagging.tf (workspace)
working-directoryRemove from top-level tagsMoved to custom_tags in tagging.tf
AIDE_0085665 (uppercase value)aide_0085665Lowercase the value (key aide-id stays)
PatchSchedulePatchScheduleKeep as-is — exception

Normalization Pattern Summary

PatternExampleNormalized
PascalCaseSolutionNamesolution-name
camelCasetechOwnertech-owner
ALLCAPSENVenv
Space SeparatedCost Centercost-center
Underscorecomponent_versioncomponent-version
Already Correctepic-appepic-app (no change)
ExceptionPatchSchedulePatchSchedule (preserved)

Checklist Per Workspace

  1. Add tagging.tf with the 16 standard tag values for this workspace.
  2. Update acn-main.tf to use local.enhanced_inputs instead of var.inputs.
  3. Remove all standard tags from inputs.tags in terraform.tfvars.json.
  4. In each deployment's tags block, keep only the 6 VM extension tags.
  5. Normalize remaining tag keys to lowercase-hyphenated (except PatchSchedule).
  6. Run terraform plan to verify no tag changes are introduced.