Cloud FinOps in 2026: Cost Tagging, Rightsizing, and Eliminating Waste
Control cloud costs with FinOps: AWS cost allocation tags, rightsizing recommendations, reserved instance strategy, Terraform cost estimation, and eliminating common waste patterns.
Cloud FinOps in 2026: Cost Tagging, Rightsizing, and Eliminating Waste
Most engineering teams have no idea what their cloud infrastructure costs per feature, per team, or per customer. They get a monthly AWS bill and try to attribute it after the fact — which is nearly impossible without a tagging strategy designed upfront.
FinOps (Financial Operations) is the discipline of making cloud spend visible, accountable, and optimizable. This post covers the three fundamentals: tagging everything consistently, rightsizing underutilized resources, and finding the quick wins that typically cut 20–40% of cloud spend without touching architecture.
The Cost Visibility Problem
Monthly AWS bill: $47,000
Questions you can't answer without tagging:
- How much does the payments feature cost to run?
- Which team is responsible for the $8,000 data transfer line item?
- What does it cost to serve one customer?
- Which environment (dev/staging/prod) is most expensive?
- Are we being charged for resources no one is using?
Without tags, the bill is a lump sum. With proper tagging, every dollar is attributable.
Tagging Strategy: The Minimum Viable Taxonomy
# Mandatory tags on every AWS resource
Required tags:
Environment: "production" | "staging" | "development" | "sandbox"
Team: "platform" | "payments" | "growth" | "data"
Service: "orders-api" | "auth-service" | "analytics-pipeline"
CostCenter: "engineering" | "data-science" | "infrastructure"
ManagedBy: "terraform" | "manual" | "cdk"
Optional but recommended:
Customer: customer ID (for customer-specific infrastructure)
Feature: feature flag or project name
Expiry: ISO date (for sandbox/temporary resources)
Enforcing Tags with AWS Config
# terraform/tag-enforcement.tf
resource "aws_config_config_rule" "required_tags" {
name = "required-tags"
source {
owner = "AWS"
source_identifier = "REQUIRED_TAGS"
}
input_parameters = jsonencode({
tag1Key = "Environment"
tag2Key = "Team"
tag3Key = "Service"
tag4Key = "CostCenter"
})
scope {
compliance_resource_types = [
"AWS::EC2::Instance",
"AWS::RDS::DBInstance",
"AWS::ElasticLoadBalancingV2::LoadBalancer",
"AWS::S3::Bucket",
"AWS::ECS::Service",
"AWS::Lambda::Function",
]
}
}
# Alert when untagged resources are created
resource "aws_config_remediation_configuration" "auto_tag" {
config_rule_name = aws_config_config_rule.required_tags.name
target_type = "SSM_DOCUMENT"
target_id = "AWS-AddTagsToResources"
automatic = false # Don't auto-tag; alert instead
parameter {
name = "AutomationAssumeRole"
static_value = aws_iam_role.config_remediation.arn
}
}
Tagging in Terraform: Default Tags
# providers.tf — default tags on all AWS resources
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
ManagedBy = "terraform"
Team = var.team
CostCenter = var.cost_center
TerraformWorkspace = terraform.workspace
}
}
}
# Resource-specific tags supplement defaults
resource "aws_ecs_service" "orders_api" {
name = "orders-api"
# ... config ...
tags = {
Service = "orders-api"
# Default tags (Environment, Team, etc.) auto-applied by provider
}
}
Tagging GitHub Actions Resources
# .github/workflows/deploy.yml — tag resources created in CI
env:
AWS_DEFAULT_TAGS: |
{
"Environment": "${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}",
"Team": "platform",
"Service": "${{ github.event.repository.name }}",
"DeployedBy": "github-actions",
"CommitSha": "${{ github.sha }}"
}
☁️ Is Your Cloud Costing Too Much?
Most teams overspend 30–40% on cloud — wrong instance types, no reserved pricing, bloated storage. We audit, right-size, and automate your infrastructure.
- AWS, GCP, Azure certified engineers
- Infrastructure as Code (Terraform, CDK)
- Docker, Kubernetes, GitHub Actions CI/CD
- Typical audit recovers $500–$3,000/month in savings
Cost Attribution with AWS Cost Explorer API
// scripts/cost-report.ts — weekly cost report per team
import {
CostExplorerClient,
GetCostAndUsageCommand,
Granularity,
} from '@aws-sdk/client-cost-explorer';
const client = new CostExplorerClient({ region: 'us-east-1' });
interface TeamCost {
team: string;
totalCost: number;
services: { service: string; cost: number }[];
}
async function getTeamCosts(startDate: string, endDate: string): Promise<TeamCost[]> {
const response = await client.send(
new GetCostAndUsageCommand({
TimePeriod: { Start: startDate, End: endDate },
Granularity: Granularity.MONTHLY,
GroupBy: [
{ Type: 'TAG', Key: 'Team' },
{ Type: 'DIMENSION', Key: 'SERVICE' },
],
Metrics: ['UnblendedCost'],
Filter: {
Not: {
Dimensions: {
Key: 'RECORD_TYPE',
Values: ['Credit', 'Refund'],
},
},
},
}),
);
const teamMap = new Map<string, TeamCost>();
for (const result of response.ResultsByTime ?? []) {
for (const group of result.Groups ?? []) {
const [teamTag, service] = group.Keys ?? [];
const team = teamTag.replace('Team$', '') || 'untagged';
const cost = parseFloat(group.Metrics?.UnblendedCost?.Amount ?? '0');
if (!teamMap.has(team)) {
teamMap.set(team, { team, totalCost: 0, services: [] });
}
const teamData = teamMap.get(team)!;
teamData.totalCost += cost;
teamData.services.push({ service, cost });
}
}
return Array.from(teamMap.values()).sort((a, b) => b.totalCost - a.totalCost);
}
// Run weekly and post to Slack
async function sendWeeklyCostReport() {
const end = new Date().toISOString().split('T')[0];
const start = new Date(Date.now() - 7 * 86400000).toISOString().split('T')[0];
const costs = await getTeamCosts(start, end);
const total = costs.reduce((s, t) => s + t.totalCost, 0);
const untagged = costs.find((t) => t.team === 'untagged');
const message = [
`*Weekly Cloud Cost Report (${start} → ${end})*`,
`Total: $${total.toFixed(2)}`,
'',
'*By Team:*',
...costs
.filter((t) => t.team !== 'untagged')
.map((t) => ` • ${t.team}: $${t.totalCost.toFixed(2)}`),
'',
untagged
? `⚠️ Untagged resources: $${untagged.totalCost.toFixed(2)} — review and tag`
: '✅ No untagged resources',
].join('\n');
await slack.chat.postMessage({ channel: '#engineering-costs', text: message });
}
Rightsizing: Finding Underutilized Resources
EC2 / ECS CPU and Memory
# scripts/rightsizing_scan.py
import boto3
from datetime import datetime, timedelta
from dataclasses import dataclass
cloudwatch = boto3.client('cloudwatch', region_name='us-east-1')
ec2 = boto3.client('ec2', region_name='us-east-1')
@dataclass
class RightsizingRecommendation:
resource_id: str
resource_type: str
current_type: str
avg_cpu_pct: float
avg_memory_pct: float
recommendation: str
monthly_savings: float
def get_ec2_utilization(instance_id: str, days: int = 14) -> dict:
end = datetime.utcnow()
start = end - timedelta(days=days)
def get_metric(metric_name: str) -> float:
response = cloudwatch.get_metric_statistics(
Namespace='AWS/EC2',
MetricName=metric_name,
Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
StartTime=start,
EndTime=end,
Period=86400, # Daily
Statistics=['Average'],
)
datapoints = response['Datapoints']
if not datapoints:
return 0.0
return sum(d['Average'] for d in datapoints) / len(datapoints)
return {
'cpu': get_metric('CPUUtilization'),
'network_in': get_metric('NetworkIn'),
}
def scan_for_oversized_instances() -> list[RightsizingRecommendation]:
recommendations = []
instances = ec2.describe_instances(
Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]
)
for reservation in instances['Reservations']:
for instance in reservation['Instances']:
instance_id = instance['InstanceId']
instance_type = instance['InstanceType']
util = get_ec2_utilization(instance_id)
avg_cpu = util['cpu']
# Flag if consistently below 15% CPU
if avg_cpu < 15:
# Suggest smaller instance type
current_cost = INSTANCE_COSTS.get(instance_type, 0)
suggested_type = suggest_smaller(instance_type)
suggested_cost = INSTANCE_COSTS.get(suggested_type, 0)
monthly_savings = (current_cost - suggested_cost) * 730 # hours/month
recommendations.append(RightsizingRecommendation(
resource_id=instance_id,
resource_type='EC2',
current_type=instance_type,
avg_cpu_pct=avg_cpu,
avg_memory_pct=0, # Requires CloudWatch agent
recommendation=f"Downsize to {suggested_type}",
monthly_savings=monthly_savings,
))
return sorted(recommendations, key=lambda r: r.monthly_savings, reverse=True)
# Instance cost map (on-demand, us-east-1, 2026 pricing)
INSTANCE_COSTS = {
'm7i.large': 0.1008,
'm7i.xlarge': 0.2016,
'm7i.2xlarge': 0.4032,
'm7i.4xlarge': 0.8064,
'c7i.large': 0.0850,
'c7i.xlarge': 0.1700,
'r7i.large': 0.1260,
'r7i.xlarge': 0.2520,
}
def suggest_smaller(instance_type: str) -> str:
DOWNSIZE_MAP = {
'm7i.2xlarge': 'm7i.xlarge',
'm7i.xlarge': 'm7i.large',
'c7i.2xlarge': 'c7i.xlarge',
'c7i.xlarge': 'c7i.large',
'r7i.2xlarge': 'r7i.xlarge',
'r7i.xlarge': 'r7i.large',
}
return DOWNSIZE_MAP.get(instance_type, instance_type)
⚙️ DevOps Done Right — Zero Downtime, Full Automation
Ship faster without breaking things. We build CI/CD pipelines, monitoring stacks, and auto-scaling infrastructure that your team can actually maintain.
- Staging + production environments with feature flags
- Automated security scanning in the pipeline
- Uptime monitoring + alerting + runbook automation
- On-call support handover docs included
Common Cloud Waste Patterns
| Waste Pattern | How to Detect | Typical Savings |
|---|---|---|
| Idle EC2 instances (<5% CPU 2+ weeks) | CloudWatch CPU metrics | $50–$500/month each |
| Unattached EBS volumes | aws ec2 describe-volumes --filters State=available | $0.10/GB/month |
| Unused Elastic IPs | Not associated with a running instance | $0.005/hour = $3.6/month each |
| Old EBS snapshots | Snapshots >90 days with no associated AMI | $0.05/GB/month |
| Idle RDS instances | <1 connection/day for 2+ weeks | $100–$2,000/month |
| Oversized RDS | CPU <10%, memory <20% | 30–60% of DB cost |
| Data transfer from wrong region | Cost Explorer: Data Transfer line | Often 10–20% of total bill |
| CloudWatch Logs retention | Logs never expire | $0.03/GB/month storage |
| NAT Gateway data processing | Route traffic via S3/DynamoDB endpoints | $0.045/GB |
Automated Waste Scan
#!/bin/bash
# scripts/find-waste.sh
echo "=== Unattached EBS Volumes ==="
aws ec2 describe-volumes \
--filters "Name=status,Values=available" \
--query 'Volumes[*].{ID:VolumeId,Size:Size,Created:CreateTime,Tags:Tags}' \
--output table
echo "=== Unused Elastic IPs ==="
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null].{IP:PublicIp,AllocationId:AllocationId}' \
--output table
echo "=== Old Snapshots (>90 days) ==="
aws ec2 describe-snapshots \
--owner-ids self \
--query "Snapshots[?StartTime<='$(date -d '90 days ago' -u +%Y-%m-%dT%H:%M:%SZ)'].{ID:SnapshotId,Size:VolumeSize,Date:StartTime}" \
--output table
echo "=== CloudWatch Log Groups (no retention set) ==="
aws logs describe-log-groups \
--query 'logGroups[?!retentionInDays].{Name:logGroupName,Size:storedBytes}' \
--output table
Reserved Instances and Savings Plans
| Commitment | Discount vs On-Demand | Best For |
|---|---|---|
| No commitment | 0% | Dev/sandbox |
| 1-year Compute Savings Plan | 40–50% | Predictable workloads |
| 3-year Compute Savings Plan | 55–66% | Long-term baseline |
| 1-year EC2 Reserved (standard) | 40–55% | Specific instance type lock-in |
| Spot Instances | 70–90% | Stateless, interruption-tolerant |
Strategy for 2026: Cover your steady-state compute baseline with 1-year Compute Savings Plans (flexible across EC2, Fargate, Lambda). Use Spot for batch jobs, training, and CI workers. Keep 20–30% on-demand for burst capacity.
Expected Savings: Typical FinOps Audit
| Company Size | Annual AWS Spend | Common Waste | Expected Savings |
|---|---|---|---|
| Early startup | $50K | Oversized dev instances | 15–25% |
| Series A | $200K | No reservations, idle resources | 25–40% |
| Series B | $1M | Data transfer, unused services | 20–35% |
| Enterprise | $5M+ | Reserved coverage gaps, rightsizing | 15–30% |
A disciplined FinOps program typically delivers 20–35% reduction in the first 90 days, then 5–10% annually through continuous optimization.
Working With Viprasol
Our infrastructure team runs cloud cost audits and implements FinOps programs — from tagging strategy through Reserved Instance purchasing and continuous rightsizing automation.
What we deliver:
- Tagging taxonomy design and Terraform enforcement
- AWS Cost Explorer attribution by team, service, and environment
- Automated waste scan (unattached volumes, idle instances, old snapshots)
- Rightsizing analysis with monthly savings estimates
- Reserved Instance / Savings Plan purchasing recommendation
→ Discuss your cloud cost reduction goals → Cloud infrastructure services
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need DevOps & Cloud Expertise?
Scale your infrastructure with confidence. AWS, GCP, Azure certified team.
Free consultation • No commitment • Response within 24 hours
Making sense of your data at scale?
Viprasol builds end-to-end big data analytics solutions — ETL pipelines, data warehouses on Snowflake or BigQuery, and self-service BI dashboards. One reliable source of truth for your entire organisation.