Implementing FinOps on AWS: A Comprehensive Guide Using Native Tools

AWS provides a broad set of native tools for running FinOps at scale. This guide walks step by step through building a FinOps practice with AWS-native services only, covering cost visibility, optimization and governance in line with the FinOps Foundation’s Inform, Optimize and Operate phases.

Bottom line: for most AWS-centric organizations, native tools cover the core FinOps capabilities without extra licensing. Third-party platforms earn their place mainly for multi-cloud, SaaS or advanced allocation needs.

The AWS FinOps Toolkit

Core Cost Management Tools

  • AWS Cost Explorer: cost analysis and forecasting
  • AWS Budgets: cost, usage and commitment budgets with alerts and actions
  • AWS Data Exports: CUR 2.0, FOCUS 1.2, recommendations and carbon emissions data, delivered to Amazon S3 (legacy CUR remains available)
  • AWS Cost Anomaly Detection: detection of unusual spend
  • AWS Cost Categories: map accounts, tags and services to your business structure
  • Billing and Cost Management Dashboards: custom and managed cost dashboards
  • AWS Billing Conductor: pro forma billing for showback and chargeback

Optimization and Rightsizing Tools

  • Cost Optimization Hub: rightsizing, idle resource and commitment recommendations across accounts and Regions, deduplicated and priced with your discounts
  • AWS Compute Optimizer: rightsizing for EC2, Auto Scaling groups, EBS, Lambda, ECS on Fargate, RDS and more
  • AWS Trusted Advisor: core checks for everyone, the full set with Business Support+ or higher; many cost checks now use Cost Optimization Hub data
  • Savings Plans and Reserved Instances: Compute, EC2 Instance, Database and SageMaker AI Savings Plans; Standard and Convertible RIs

Governance and Automation Tools

  • AWS Organizations: consolidated billing, tag policies and service control policies (SCPs)
  • AWS Config: configuration and tag compliance tracking
  • AWS CloudFormation: infrastructure as code; pair it with AWS Pricing Calculator estimates
  • AWS Lambda and Amazon EventBridge: custom cost automation
  • Instance Scheduler on AWS: scheduled start and stop for EC2, Auto Scaling groups and RDS

Phase 1: Foundation Setup – Building Cost Visibility

Step 1: Configure CUR 2.0 Through AWS Data Exports

CUR 2.0 is the most detailed billing dataset on AWS. Unlike legacy CUR, it has a fixed schema, nested columns for tags and cost categories, and basic SQL for selecting columns. Create it in the management account to cover the whole organization.

  1. In the Billing and Cost Management console, open Data Exports and create a standard export with the CUR 2.0 table.
  2. Include resource IDs, plus split cost allocation data if you run Amazon ECS or EKS.
  3. Select Parquet and the overwrite option to keep storage and query costs down.
  4. Deliver to a dedicated S3 bucket with versioning, lifecycle rules and a bucket policy for the Data Exports service (the console can create it).
  5. For multi-cloud reporting, add a second export using FOCUS 1.2 with AWS columns.

The same export as code, using the AWS CLI:

aws bcm-data-exports create-export --cli-input-json file://cur2-export.json
{
  "Export": {
    "Name": "org-cur2-daily",
    "DataQuery": {
      "QueryStatement": "SELECT bill_billing_period_start_date, line_item_usage_account_id, line_item_usage_account_name, line_item_product_code, line_item_usage_type, line_item_resource_id, line_item_line_item_type, line_item_unblended_cost, pricing_term, resource_tags, cost_category FROM COST_AND_USAGE_REPORT",
      "TableConfigurations": {
        "COST_AND_USAGE_REPORT": {
          "TIME_GRANULARITY": "DAILY",
          "INCLUDE_RESOURCES": "TRUE",
          "INCLUDE_SPLIT_COST_ALLOCATION_DATA": "FALSE"
        }
      }
    },
    "DestinationConfigurations": {
      "S3Destination": {
        "S3Bucket": "your-org-cost-exports",
        "S3Prefix": "cur2",
        "S3Region": "your-bucket-region",
        "S3OutputConfigurations": {
          "OutputType": "CUSTOM",
          "Format": "PARQUET",
          "Compression": "PARQUET",
          "Overwrite": "OVERWRITE_REPORT"
        }
      }
    },
    "RefreshCadence": { "Frequency": "SYNCHRONOUS" }
  }
}

Query the export with Amazon Athena, or use the Cost and usage dashboard export type for a prebuilt QuickSight dashboard. Top cost drivers by account and service (adjust names to your setup):

SELECT line_item_usage_account_name,
       line_item_product_code,
       SUM(line_item_unblended_cost) AS unblended_cost
FROM cost_exports.cur2
WHERE bill_billing_period_start_date = TIMESTAMP '2026-09-01 00:00:00'
GROUP BY 1, 2
ORDER BY 3 DESC
LIMIT 20;

Step 2: Implement a Tagging Strategy

Tagging is the basis of cost allocation. AWS allows up to 50 user-created tags per resource.

An example of a mandatory tag schema:

{
  "Environment": "production|staging|development|sandbox",
  "Project": "project-name",
  "Owner": "team-or-individual-email",
  "CostCenter": "finance-cost-center-code",
  "BusinessUnit": "marketing|engineering|sales|operations",
  "Application": "application-name",
  "Purpose": "web-server|database|analytics|backup",
  "Schedule": "24x7|business-hours|weekend-only|on-demand"
}

Activate tags for billing: user-defined tags appear in billing data only after you activate them as cost allocation tags; allow up to 24 hours for new keys to appear and another 24 for activation.

Use the AWS-generated aws:createdBy tag instead of a custom auto-tagging function. Once the management account activates it, AWS adds it on a best-effort basis to supported resources created afterward; it isn’t retroactive.

Enforce tags at creation with SCPs, one statement per required tag. Keys combined in a single Null condition are evaluated together, so they would only deny requests missing every tag.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyRunInstancesWithoutEnvironment",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": { "Null": { "aws:RequestTag/Environment": "true" } }
    },
    {
      "Sid": "DenyRunInstancesWithoutCostCenter",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": { "Null": { "aws:RequestTag/CostCenter": "true" } }
    },
    {
      "Sid": "DenyRunInstancesWithoutOwner",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": { "Null": { "aws:RequestTag/Owner": "true" } }
    }
  ]
}

Use tag policies in AWS Organizations to standardize values and capitalization. They can block noncompliant tagging on specified resource types, but they don’t stop untagged resources from being created.

Step 3: Configure AWS Budgets

Set up several budget types:

  • Overall budget: monthly, based on your historical baseline, with actual alerts at, for example, 50%, 80% and 100%, plus a forecast alert
  • Service budgets: for your largest services, such as EC2, RDS, S3 and data transfer
  • Tag or cost category budgets: per environment, application or cost center

A tag-based budget with email and SNS notifications (CostFilters is deprecated; use FilterExpression):

import boto3

def create_tag_budget(name, tag_key, tag_value, monthly_limit_usd, email, sns_topic_arn):
    account_id = boto3.client("sts").get_caller_identity()["Account"]
    budgets = boto3.client("budgets")

    budgets.create_budget(
        AccountId=account_id,
        Budget={
            "BudgetName": name,
            "BudgetType": "COST",
            "TimeUnit": "MONTHLY",
            "BudgetLimit": {"Amount": str(monthly_limit_usd), "Unit": "USD"},
            "FilterExpression": {
                "Tags": {"Key": tag_key, "Values": [tag_value], "MatchOptions": ["EQUALS"]}
            },
        },
        NotificationsWithSubscribers=[
            {
                "Notification": {
                    "NotificationType": "ACTUAL",
                    "ComparisonOperator": "GREATER_THAN",
                    "Threshold": 80,
                    "ThresholdType": "PERCENTAGE",
                },
                "Subscribers": [{"SubscriptionType": "EMAIL", "Address": email}],
            },
            {
                "Notification": {
                    "NotificationType": "FORECASTED",
                    "ComparisonOperator": "GREATER_THAN",
                    "Threshold": 100,
                    "ThresholdType": "PERCENTAGE",
                },
                "Subscribers": [
                    {"SubscriptionType": "EMAIL", "Address": email},
                    {"SubscriptionType": "SNS", "Address": sns_topic_arn},
                ],
            },
        ],
    )

The SNS topic policy must allow AWS Budgets to publish. Budget actions can also apply an IAM policy or SCP, or stop EC2 and RDS instances, when a threshold is crossed.

Phase 2: Deep Cost Analysis With Cost Explorer

Cost Categories for Business Alignment

Define Cost Categories in the Billing and Cost Management console so reports reflect your organization:

  • Application tiers: Web (tag Purpose = web-server), App (tag Purpose = application-server), Data (tag Purpose = database or cache), Analytics (Amazon Redshift or Amazon EMR)
  • Environment allocation: Production (tag Environment = production) and Non-production (everything else)

Cost Anomaly Detection

Create at least one monitor. AWS managed monitors pick up new services, accounts or tag values automatically; tag, account and cost category monitors must be created in the management account. In the API, use ThresholdExpression (Threshold is deprecated).

import boto3

ce = boto3.client("ce")

# AWS managed monitor that evaluates every AWS service
services = ce.create_anomaly_monitor(
    AnomalyMonitor={
        "MonitorName": "all-services",
        "MonitorType": "DIMENSIONAL",
        "MonitorDimension": "SERVICE",
    }
)

# AWS managed monitor that evaluates each value of the Environment tag
by_environment = ce.create_anomaly_monitor(
    AnomalyMonitor={
        "MonitorName": "by-environment",
        "MonitorType": "DIMENSIONAL",
        "MonitorDimension": "TAG",
        "MonitorSpecification": {"Tags": {"Key": "Environment"}},
    }
)

# Daily email summary for anomalies with a total impact of at least 100 USD
ce.create_anomaly_subscription(
    AnomalySubscription={
        "SubscriptionName": "finops-daily-anomalies",
        "MonitorArnList": [services["MonitorArn"], by_environment["MonitorArn"]],
        "Subscribers": [{"Type": "EMAIL", "Address": "[email protected]"}],
        "Frequency": "DAILY",
        "ThresholdExpression": {
            "Dimensions": {
                "Key": "ANOMALY_TOTAL_IMPACT_ABSOLUTE",
                "MatchOptions": ["GREATER_THAN_OR_EQUAL"],
                "Values": ["100"],
            }
        },
    }
)

For individual alerts, use IMMEDIATE frequency with an SNS topic, which Amazon Q Developer in chat applications can route to Slack or Microsoft Teams.

Phase 3: Resource Optimization With Native Tools

Enable Compute Optimizer and Cost Optimization Hub

Opt in from the management account to cover all member accounts. Enhanced infrastructure metrics is a paid option that extends the lookback from 14 to up to 93 days.

import boto3

boto3.client("compute-optimizer").update_enrollment_status(
    status="Active", includeMemberAccounts=True
)
boto3.client("cost-optimization-hub").update_enrollment_status(
    status="Active", includeMemberAccounts=True
)

# Optional, paid: longer lookback for EC2 recommendations
boto3.client("compute-optimizer").put_recommendation_preferences(
    resourceType="Ec2Instance",
    enhancedInfrastructureMetrics="Active",
)

Process Recommendations From Cost Optimization Hub

Cost Optimization Hub returns one deduplicated recommendation per resource, with savings that account for your commitments, which makes it the best source for automation. This example emails a weekly summary of low-effort rightsizing and stop recommendations; it reports, it doesn’t resize.

import json
import boto3

coh = boto3.client("cost-optimization-hub")
sns = boto3.client("sns")

def weekly_rightsizing_summary(topic_arn, min_monthly_savings=50):
    args = {
        "filter": {
            "actionTypes": ["Rightsize", "Stop"],
            "implementationEfforts": ["VeryLow", "Low"],
            "tags": [{"key": "AutoOptimize", "value": "true"}],
        },
        "orderBy": {"dimension": "estimatedMonthlySavings", "order": "Desc"},
        "includeAllRecommendations": False,
        "maxResults": 100,
    }
    items, token = [], None
    while True:
        page = coh.list_recommendations(**args, **({"nextToken": token} if token else {}))
        items += [i for i in page["items"] if i["estimatedMonthlySavings"] >= min_monthly_savings]
        token = page.get("nextToken")
        if not token:
            break

    summary = {
        "count": len(items),
        "estimated_monthly_savings": round(sum(i["estimatedMonthlySavings"] for i in items), 2),
        "items": [
            {
                "account": i["accountId"],
                "resource": i["resourceId"],
                "action": i["actionType"],
                "from": i.get("currentResourceSummary"),
                "to": i.get("recommendedResourceSummary"),
                "restart_needed": i["restartNeeded"],
            }
            for i in items
        ],
    }
    sns.publish(TopicArn=topic_arn, Subject="Weekly rightsizing summary",
                Message=json.dumps(summary, indent=2, default=str))

Savings Plans and Reserved Instances

Rightsize first, then commit for the stable baseline. AWS recommends Savings Plans over Reserved Instances for compute, and Database Savings Plans now cover services such as Aurora, RDS, DynamoDB and ElastiCache. For commitment recommendations, reuse the function above with the PurchaseSavingsPlans and PurchaseReservedInstances action types, and check them against your roadmap before buying.

Phase 4: Advanced Governance and Automation

Cost Control Policies

An SCP can block instance families that need approval, such as large GPU and high-memory instances. Grant exceptions through a dedicated organizational unit.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyUnapprovedHighCostInstanceFamilies",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringLike": {
          "ec2:InstanceType": ["p4*", "p5*", "x2*", "u-*"]
        }
      }
    }
  ]
}

Automated Cost Optimization Workflows

Schedule non-production resources with Instance Scheduler on AWS, or a small Lambda function that uses your business time zone rather than UTC:

from datetime import datetime
from zoneinfo import ZoneInfo
import boto3

ec2 = boto3.client("ec2")
TZ = ZoneInfo("Europe/Madrid")  # needs time zone data; package tzdata if your runtime lacks it

def outside_business_hours(now):
    # Business hours: 08:00-18:00, Monday to Friday
    return now.weekday() >= 5 or now.hour < 8 or now.hour >= 18

def lambda_handler(event, context):
    if not outside_business_hours(datetime.now(TZ)):
        return
    paginator = ec2.get_paginator("describe_instances")
    for page in paginator.paginate(Filters=[
        {"Name": "tag:Schedule", "Values": ["business-hours"]},
        {"Name": "instance-state-name", "Values": ["running"]},
    ]):
        ids = [i["InstanceId"] for r in page["Reservations"] for i in r["Instances"]]
        if ids:
            ec2.stop_instances(InstanceIds=ids)

Clean up unused resources carefully. Cost Optimization Hub already flags many idle resources. If you script cleanup, report first, snapshot before deleting, and remember that a volume’s CreateTime isn’t its detach time.

from datetime import datetime, timedelta, timezone
import boto3

ec2 = boto3.client("ec2")
cw = boto3.client("cloudwatch")
elbv2 = boto3.client("elbv2")

def report_unused(delete=False, min_age_days=30):
    cutoff = datetime.now(timezone.utc) - timedelta(days=min_age_days)

    # Unattached EBS volumes (status "available")
    for page in ec2.get_paginator("describe_volumes").paginate(
        Filters=[{"Name": "status", "Values": ["available"]}]
    ):
        for vol in page["Volumes"]:
            tags = {t["Key"]: t["Value"] for t in vol.get("Tags", [])}
            if tags.get("DoNotDelete", "").lower() == "true" or vol["CreateTime"] > cutoff:
                continue
            print(f"Unattached volume {vol['VolumeId']} ({vol['Size']} GiB)")
            if delete:
                snap = ec2.create_snapshot(VolumeId=vol["VolumeId"],
                                           Description="Pre-deletion backup")
                ec2.get_waiter("snapshot_completed").wait(SnapshotIds=[snap["SnapshotId"]])
                ec2.delete_volume(VolumeId=vol["VolumeId"])

    # Application Load Balancers with no requests in the last 7 days
    end = datetime.now(timezone.utc)
    for page in elbv2.get_paginator("describe_load_balancers").paginate():
        for lb in page["LoadBalancers"]:
            if lb["Type"] != "application":
                continue
            dimension = lb["LoadBalancerArn"].split(":loadbalancer/")[1]  # app/name/id
            stats = cw.get_metric_statistics(
                Namespace="AWS/ApplicationELB", MetricName="RequestCount",
                Dimensions=[{"Name": "LoadBalancer", "Value": dimension}],
                StartTime=end - timedelta(days=7), EndTime=end,
                Period=86400, Statistics=["Sum"],
            )
            if sum(p["Sum"] for p in stats["Datapoints"]) == 0:
                print(f"Idle load balancer {lb['LoadBalancerName']}: review")

Phase 5: Reporting and Analytics

Use what AWS provides before building custom dashboards:

  • Billing and Cost Management Dashboards: custom dashboards, plus five managed dashboards added in August 2026 at no additional cost, which you can copy, schedule and share
  • Cost and usage dashboard: a QuickSight dashboard deployed through Data Exports
  • Natural-language analysis: Amazon Q Developer can answer cost questions in the console

Cost Explorer API calls are billed per request, so pull on a schedule and cache the results. A monthly breakdown by service and environment (end date exclusive):

import boto3

ce = boto3.client("ce")

def monthly_costs(start="2026-09-01", end="2026-10-01"):
    by_service = ce.get_cost_and_usage(
        TimePeriod={"Start": start, "End": end},
        Granularity="MONTHLY",
        Metrics=["UnblendedCost", "AmortizedCost"],
        GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
    )
    by_environment = ce.get_cost_and_usage(
        TimePeriod={"Start": start, "End": end},
        Granularity="MONTHLY",
        Metrics=["AmortizedCost"],
        GroupBy=[{"Type": "TAG", "Key": "Environment"}],
    )
    return by_service["ResultsByTime"], by_environment["ResultsByTime"]

Implementation Roadmap

Rough planning ranges for one AWS organization; large or regulated estates take longer.

Weeks 1–2: Foundation

  • Create CUR 2.0 (and, if needed, FOCUS) exports
  • Agree on the tagging strategy and activate cost allocation tags
  • Configure AWS Budgets
  • Set up Cost Anomaly Detection monitors and subscriptions

Weeks 3–4: Analysis Tools

  • Define Cost Categories
  • Enable Compute Optimizer and Cost Optimization Hub organization-wide
  • Build the first recommendation reports

Weeks 5–6: Governance

  • Deploy tag and cost control SCPs and tag policies
  • Set up scheduling for non-production resources
  • Introduce cleanup automation in report mode
  • Add an approval path for high-cost resources

Weeks 7–8: Reporting and Optimization

  • Publish dashboards and a regular reporting cadence
  • Tune automation rules
  • Train teams on the new processes

If you’re migrating, put tagging and budgets in place before the first wave; see lessons learned from application migrations.

Best Practices and Pro Tips

Tagging

  • Use consistent naming conventions across all accounts
  • Standardize values with tag policies in AWS Organizations
  • Audit tag compliance with AWS Config
  • Tag at creation through infrastructure as code rather than after the fact

Cost Optimization

  • Start with the largest cost drivers
  • Commit only for predictable baseline usage, after rightsizing
  • Use Spot Instances for fault-tolerant workloads
  • Scale on demand patterns, not peak estimates

Automation

  • Build safety checks and dry-run modes into every script
  • Handle errors and log every action
  • Monitor the automation itself for failures

Organizational Change

  • Train teams regularly on cloud economics
  • Make cost visible to development teams
  • Assign clear ownership for each cost center
  • Share and recognize optimization wins

Measuring Success

KPI categoryExamples
Cost efficiencyCost per application or service, month-over-month trends, Savings Plans and RI utilization and coverage, waste reduction
OperationsTime to detect anomalies, number of automated optimizations, tag compliance, share of recommendations implemented
Business valueSavings achieved, time saved on cost management, allocation accuracy, budget freed for new initiatives

Conclusion

Native AWS tools give you a solid, low-cost FinOps foundation. Success comes from strong fundamentals (accurate tagging and CUR 2.0 data), incremental automation and a focus on business value rather than cost reduction alone.

Start with Data Exports and tagging, then add budgets, anomaly detection and Cost Optimization Hub, and keep refining as your organization and AWS’s cost services evolve.

Sources

Last reviewed: October 2026.

Leave a Reply

Rares Juretchi

Rares Juretchi

Rares is a cloud and enterprise architect with over 25 years of IT experience. He has led or architected over 100 cloud programs in regulated industries, including healthcare, finance, manufacturing and energy, and specializes in Azure landing zones, migrations and governance, with AWS alongside.

The blog offers practical guidance on:

  • Cloud migration strategy and execution
  • EU digital regulation (NIS2, DORA, the AI Act and more) for cloud teams
  • Cloud security frameworks and compliance
  • Landing zones and cloud governance

Read the full profile →

Let’s connect

Discover more from Information Technology Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading