Back to Blog
    Financial Management

    AWS Cost Allocation Tags: Best Practices for Financial Visibility

    Kostiantyn DementievJuly 15, 20255 min read

    The goal: a tagging scheme that maps every dollar on the bill to a team, a product, or an environment that somebody owns. Without it, cost reduction is guesswork, because nobody can tell you who the spend belongs to.

    An untagged AWS account tells you how much you are spending and nothing else. You cannot chargeback, you cannot set a per-team budget, and when the bill jumps you have no way to narrow the search. Tagging is the unglamorous prerequisite for every other piece of cost work.

    Essential tag categories

    Business tags

    • CostCenter: finance department code
    • Project: project identifier
    • Team: owning team name
    • BusinessUnit: department or division

    Technical tags

    • Environment: prod, staging, dev
    • Application: application name
    • Service: service identifier
    • Version: application version

    Implementing the strategy

    Step 1: define the taxonomy

    Write the schema down before anybody applies a tag. Case, allowed values, and who owns each key all need to be settled first, because retagging later is a migration.

    Example tag structure

    {
      "CostCenter": "CC-1001",
      "Project": "CustomerPortal",
      "Team": "WebDev",
      "Environment": "production",
      "Application": "customer-portal",
      "Service": "user-api",
      "Owner": "john.doe@company.com",
      "CreatedBy": "terraform",
      "Backup": "daily",
      "Compliance": "pci-dss"
    }

    Step 2: enforce with policies

    Voluntary tagging decays. Use AWS Organizations Service Control Policies to refuse resource creation without the mandatory keys:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Deny",
          "Action": [
            "ec2:RunInstances",
            "rds:CreateDBInstance",
            "s3:CreateBucket"
          ],
          "Resource": "*",
          "Condition": {
            "Null": {
              "aws:RequestedRegion": "false",
              "aws:RequestTag/CostCenter": "true",
              "aws:RequestTag/Project": "true",
              "aws:RequestTag/Environment": "true"
            }
          }
        }
      ]
    }

    Step 3: automate

    Tag Policies and AWS Config cover drift. For everything created outside Terraform, an event-driven Lambda can apply the defaults:

    # Lambda function to auto-tag resources
    import boto3
    import json
    
    def lambda_handler(event, context):
        ec2 = boto3.resource('ec2')
    
        # Extract instance creation details
        instance_id = event['detail']['instance-id']
        instance = ec2.Instance(instance_id)
    
        # Apply default tags
        instance.create_tags(
            Tags=[
                {'Key': 'AutoTagged', 'Value': 'true'},
                {'Key': 'CreatedBy', 'Value': 'lambda-auto-tagger'},
                {'Key': 'CreatedDate', 'Value': event['time']}
            ]
        )

    Cost reporting with tags

    Activate each key as a cost allocation tag in the billing console first. Until you do, Cost Explorer will not group by it, and the data does not backfill:

    import boto3
    from datetime import datetime, timedelta
    
    def get_cost_by_tag(tag_key, start_date, end_date):
        ce = boto3.client('ce')
    
        response = ce.get_cost_and_usage(
            TimePeriod={
                'Start': start_date.strftime('%Y-%m-%d'),
                'End': end_date.strftime('%Y-%m-%d')
            },
            Granularity='MONTHLY',
            Metrics=['UnblendedCost'],
            GroupBy=[
                {
                    'Type': 'TAG',
                    'Key': tag_key
                }
            ]
        )
    
        return response

    Common pitfalls

    Inconsistent tag values

    Problem: "Prod", "Production", and "PROD" become three separate cost buckets.

    Fix: tag policies that pin the allowed values and the casing.

    Too many tags

    Problem: a twenty key schema nobody fills in correctly is worse than a five key one everybody does.

    Fix: start with 5 to 7 keys and add more only when a specific question needs them.

    Legacy resources

    Problem: everything created before the policy has no tags at all.

    Fix: bulk apply with the Resource Groups Tagging API, working down from the largest line items.

    Monitoring tag compliance

    An AWS Config rule reports which resources are missing required tags, so compliance becomes a number you can track rather than an assumption:

    # AWS Config rule for required tags
    aws configservice put-config-rule \
      --config-rule '{
        "ConfigRuleName": "required-tags",
        "Source": {
          "Owner": "AWS",
          "SourceIdentifier": "REQUIRED_TAGS"
        },
        "InputParameters": "{\"tag1Key\":\"CostCenter\",\"tag2Key\":\"Project\",\"tag3Key\":\"Environment\"}"
      }'

    Untagged spend is one of the first things we flag in our AWS cost audit, which comes at a fixed price.

    Where to start

    Do not try to tag everything at once. Sort the bill by line item, tag the top ten cost drivers, and put an SCP in front of new resources so the gap stops growing. That gets you a usable per-team view in a week, and the long tail can follow.