Six Azure Tags That Make Chargeback and Showback Actually Work

Six Azure Tags That Make Chargeback and Showback Actually Work

2025-10-16 · ~6 min read

One tag can’t tell the whole story. Here’s how I built a practical Azure tagging model that connects operations, finance, and security — and finally made chargeback for shared services accurate and defensible.

The story (from the trenches)

When Finance first asked for a chargeback report by Line of Business, I thought our tags would make it easy.
Every resource had Owner, Application, and CostCenter — what could go wrong?
Everything, it turns out.

Our tags told us who ran the resource, not who paid for it.
So I rebuilt the model from the ground up using six simple tags that finally made Azure Policy, FinOps, and security data line up.

Owner: Data
Application: Platform Services
CostCenter: 200-1234
Type: Server
DomainJoined: Yes
ChargebackType: Shared

It looked clean, but it told the wrong story — who operates it, not who funds it.


The minimal tag set that actually works

Tag Example Why it matters
Owner Data Operational custodian. Who runs/fixes the thing.
Application Platform Services Workload identity for slicing costs and incidents.
CostCenter 200-1234 The Line of Business that funds/consumes it. Drives showback/chargeback.
Type Server / Desktop / Appliance Inventory posture & lifecycle (patching, backup, decomm).
DomainJoined Yes / No Security & compliance signal (identity plane, GPO reach, break-glass lists).
ChargebackType Shared / Direct Flags resources that need split or direct billing.

Key idea: Owner is not the payer. CostCenter is the money tag. Application is the bridge between Ops (Owner) and Finance (CostCenter). Type and DomainJoined make your inventory and patching views accurate.


Shared services: how we made chargeback fair

Shared platforms (databases, monitoring, identity, logging) don’t map to a single payer. We used ChargebackType=Shared to mark them and cost allocation rules to split costs fairly among consuming CostCenters.

Example split (concept):

Source (tag) Target CostCenter Allocation %
ChargebackType=Shared for Application=Platform Services 200-1234 40%
" 300-5678 30%
" 400-6789 30%

Start with showback (no internal billing) for a few cycles to build trust. Move to chargeback once your tag coverage is stable.


Queries you’ll use every week

1) Find resources missing critical tags

Resources
| extend Owner=tostring(tags.Owner), App=tostring(tags.Application), CC=tostring(tags.CostCenter), Type=tostring(tags.Type), DJ=tostring(tags['DomainJoined'])
| where isempty(Owner) or isempty(App) or isempty(CC) or isempty(Type) or isempty(DJ)
| project subscriptionId, resourceGroup, name, type, Owner, App, CC, Type, DJ
| order by subscriptionId, resourceGroup, name

2) Showback view by Line of Business (CostCenter) and Application

CostByResource
| summarize CostUSD=sum(AmortizedCost) by CostCenter, Application, bin(UsageDate, 1d)
| order by UsageDate asc

3) Patch & compliance slice by Type and DomainJoined

Resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend Type=tostring(tags.Type), DJ=tostring(tags['DomainJoined'])
| summarize CountVMs=count() by Type, DJ

4) Owner ≠ Payer sanity check

Resources
| extend Owner=tostring(tags.Owner), App=tostring(tags.Application), CC=tostring(tags.CostCenter)
| summarize Resources=count() by Owner, CC
| order by Owner, CC

Azure Policy: deny on missing tags (with friendly remediation)

Require the six tags on create. Deny if missing; pair with an initiative that adds a Modify policy for auto-append in well-known RGs.

{
  "properties": {
    "displayName": "Require core tags on resources",
    "policyRule": {
      "if": {
        "anyOf": [
          { "field": "tags['Owner']", "exists": false },
          { "field": "tags['Application']", "exists": false },
          { "field": "tags['CostCenter']", "exists": false },
          { "field": "tags['Type']", "exists": false },
          { "field": "tags['DomainJoined']", "exists": false },
          { "field": "tags['ChargebackType']", "exists": false }
        ]
      },
      "then": { "effect": "deny" }
    }
  }
}

Tip: keep tag names exact (DomainJoined, not Is Domain Joined). If you’re already live with variants, standardize and migrate with a one-time script.


PowerShell: weekly tag health & nudge

Connect-AzAccount

$query = @"
Resources
| extend Owner=tostring(tags.Owner), App=tostring(tags.Application), CC=tostring(tags.CostCenter), Type=tostring(tags.Type), DJ=tostring(tags['DomainJoined']), CB=tostring(tags.ChargebackType)
| where isempty(Owner) or isempty(App) or isempty(CC) or isempty(Type) or isempty(DJ) or isempty(CB)
| project subscriptionId, resourceGroup, name, type, Owner, App, CC, Type, DJ, CB
"@

$subs = (Get-AzSubscription).Id
$arg  = @{ Query = $query; Subscriptions = $subs }
$result = Search-AzGraph @arg
$result | Export-Csv -NoTypeInformation -Path "Tag-Remediation-MissingCoreTags.csv"

Operating model that stuck

  1. Designate tag owners: each Application has a named owner responsible for fixing missing tags.
  2. Showback first: publish CostCenter/Application dashboards monthly; fix gaps.
  3. Promote to chargeback: once untagged cost <2%, switch Finance to these numbers.
  4. Hold the line: deny on create, auto-append only in controlled landing zones.

TL;DR

  • Owner tells us who runs it. CostCenter tells us who pays. Application ties the story together.
  • Add Type, DomainJoined, and ChargebackType to unlock patching, inventory, and FinOps accuracy.
  • Mark shared services and split fairly. Start with showback, then charge back.

This six-tag model is the smallest practical set that made our governance, security, and FinOps reporting finally agree with each other — and with Finance.

Get Azure tips in your inbox

Join Azure pros getting practical KQL queries, cost optimization tips, and real-world solutions.