How to Tag AWS Resources for Cost Allocation: A Step-by-Step Strategy
Most AWS tagging strategies fail in the same way. Someone writes a Confluence page listing 14 tags. Nobody enforces them. Six months later, Cost Explorer shows 60% of spend as “Untagged”, and the strategy is quietly forgotten.
This post is a playbook designed to avoid that outcome. It is intentionally narrow — five tags, not fourteen — and intentionally enforceable. By the end of it you’ll have a tagging policy that produces real cost allocation reports, and a way to actually make engineers follow it.
Step 1: Decide what you’re trying to answer
Tags are a means, not an end. Before you write a single tag, write down the questions you want to answer from cost data. Most organisations need to answer some subset of:
- How much is each team spending?
- How much is each product or service costing to run?
- How much is spent on production vs non-production?
- How much is each customer or tenant costing us? (multi-tenant SaaS only)
- Which environment (dev/staging/prod) has the most waste?
You only need a tag for each question you actually want to answer. Don’t add tags speculatively.
Step 2: The minimal viable tag set
For 90% of organisations, this five-tag schema is enough:
| Tag key | Required values | Why |
|---|---|---|
Environment | prod, staging, dev | Slice production vs the rest |
Team | Free text (e.g. payments, platform) | Assign cost to a budget owner |
Service | Free text (e.g. checkout-api) | Group resources by what they belong to |
CostCenter | Finance-supplied code | Map directly to GL |
ManagedBy | terraform, cdk, manual | Audit trail for IaC |
That’s it. Five tags. Lowercase keys. Lowercase values where they’re enums.
If you can’t fit your tagging policy on a single page, no engineer will ever read it.
Step 3: Activate user-defined cost allocation tags
This is the step most teams skip, and it is the reason their beautiful tagging strategy produces no cost data. AWS does not show your tags in Cost Explorer until you explicitly activate them.
- Sign in to the AWS management account.
- Navigate to Billing → Cost allocation tags.
- Find each tag key from your schema under User-defined cost allocation tags.
- Select them and click Activate.
Note the warning: tags become available in Cost Explorer only for usage incurred after activation. Past data will not be retroactively tagged. The sooner you activate, the sooner you have useful history.
Step 4: Enforce at provisioning time
A policy that lives in Confluence does not exist. Enforcement happens in two places: at IaC merge time, and at AWS API time.
In Terraform / OpenTofu
Use a default tag set at the provider level so engineers cannot accidentally forget:
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
Environment = var.environment
Team = var.team
Service = var.service
CostCenter = var.cost_center
ManagedBy = "terraform"
}
}
}
Then add a terraform validate pre-commit hook (or a CI check) that fails the build if any required variable is missing.
In AWS Organizations
Use Tag Policies to require specific tag keys and values across the whole organization:
{
"tags": {
"Environment": {
"tag_key": { "@@assign": "Environment" },
"tag_value": { "@@assign": ["prod", "staging", "dev"] },
"enforced_for": { "@@assign": ["ec2:instance", "rds:db", "s3:bucket"] }
}
}
}
This will block the creation of resources that don’t comply for the listed resource types. Start with one resource type and expand once teams have adapted.
Step 5: Retag what’s already there
You will have a long tail of legacy untagged resources. Don’t try to fix them all at once. Prioritise:
- Top 20 most expensive resources — fix these by hand.
- Anything tagged
Environment=prod— these matter most. - Everything else — bulk-tag using the Resource Groups Tagging API.
# Bulk-apply a default Team tag to every untagged EC2 instance
aws resourcegroupstaggingapi tag-resources \
--resource-arn-list $(aws ec2 describe-instances \
--query 'Reservations[].Instances[?!not_null(Tags[?Key==`Team`])].InstanceId' \
--output text) \
--tags Team=unowned
Tagging everything unowned is not a defeat — it’s a starting position. It surfaces orphan resources, and forces the question of who actually owns them.
Step 6: Measure what’s untagged
The single most useful metric in your tagging programme is: what percentage of spend is correctly tagged? Track it weekly.
-- Run against your AWS Cost and Usage Report (CUR) in Athena
SELECT
CASE WHEN resource_tags['user_team'] IS NULL THEN 'untagged' ELSE 'tagged' END AS tag_status,
SUM(line_item_unblended_cost) AS cost
FROM cur_table
WHERE line_item_usage_start_date >= date_add('day', -30, current_date)
GROUP BY 1;
A healthy organisation hits >95% tagged spend within three months. If you’re stuck below 80%, the problem is almost always enforcement, not policy.
Common mistakes to avoid
- Inconsistent casing.
Team,team, andTEAMare three different tags to AWS. Pick one. Document it. Lint it. - Too many tags. Twelve tags is not three times more useful than four tags. It is fifty times less likely to be followed.
- No expiry tags for temporary resources. Add an
ExpiresOntag (ISO date) for anything POC-related, and run a Lambda nightly that flags or terminates expired resources. - Tagging individual resources but not their associated ones. An EC2 instance carries tags. Its EBS volumes do not automatically inherit them. Use
copy_tags_to_volumeand equivalents.
Where to go from here
A tagging policy is not a one-time project. It is a continuous engineering practice — like dependency upgrades or security patching. Build it into your onboarding, your code review, and your sprint reviews.
If you’d like a hand designing or auditing one for your AWS environment, get in touch. Tagging gaps are usually one of the first things to surface in an audit — and one of the highest-leverage things to fix.