Multi-account AWS cost visibility: the tagging strategy that actually works
The gap between "we have a billing dashboard" and "we understand our costs" is almost always a tagging problem.
The tag taxonomy
Required (enforced via AWS Config):
Project = "payment-api" | "user-service"
Environment = "prod" | "staging" | "dev"
Team = "platform" | "backend"
ManagedBy = "terraform" | "manual"
Terraform: default_tags (auto-applies to every resource)
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
Project = var.project_name
Environment = var.environment
Team = var.team
ManagedBy = "terraform"
}
}
}
# All resources now inherit these tags — no per-resource tagging needed
Enforce via AWS Config
resource "aws_config_config_rule" "required_tags" {
name = "required-tags"
source { owner = "AWS"; source_identifier = "REQUIRED_TAGS" }
input_parameters = jsonencode({
tag1Key = "Project"; tag2Key = "Environment"; tag3Key = "Team"
})
}
Query cost by project (Lambda)
import boto3
def get_cost_by_project(account_id: str) -> dict:
ce = boto3.client('ce', region_name='us-east-1')
response = ce.get_cost_and_usage(
TimePeriod={'Start': '2025-01-01', 'End': '2025-02-01'},
Granularity='MONTHLY',
Filter={'Dimensions': {'Key': 'LINKED_ACCOUNT', 'Values': [account_id]}},
GroupBy=[{'Type': 'TAG', 'Key': 'Project'}],
Metrics=['UnblendedCost']
)
return {
g['Keys'][0].replace('Project$', ''):
round(float(g['Metrics']['UnblendedCost']['Amount']), 2)
for g in response['ResultsByTime'][0]['Groups']
}
Most common tagging failures
- Tags on launch, never updated — use Config rules to catch untagged resources
- Different values for same concept ("prod", "production", "PROD") — enforce allowed values
- EC2 tagged but not EBS volumes — add
volume_tagsin Terraform provider - Tags not activated in Cost Explorer — takes 24 hours after activation
Per-project cost visibility across accounts is coming to Step2Dev in Q1.
Top comments (0)