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
Recommended Reading
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.
How Viprasol Helps
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
External Resources
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 1000+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement.
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.