Infrastructure costs often scale faster than traffic because resources are provisioned pessimistically and never rightsized after the initial setup. Claude Code helps analyze cost patterns, identify waste, and implement reductions: database queries generating unnecessary load, idle EC2 instances, N+1 queries causing excessive RDS costs, and CDN misconfiguration causing expensive origin traffic.
This guide covers cost optimization with Claude Code: identifying waste, rightsizing, query optimization, and automated cost monitoring.
Cost Analysis Workflow
Our AWS bill jumped 40% last month.
Help me identify the cause and what to cut.
# Export cost data from AWS Cost Explorer
aws ce get-cost-and-usage \
--time-period Start=2026-03-01,End=2026-04-01 \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=SERVICE \
--output json > cost-report.json
Analyze this AWS cost report (paste JSON).
Identify:
1. Which services grew most month-over-month
2. Any services I'm likely not using
3. Quick wins for cost reduction
Common findings Claude Code surfaces:
- RDS read replicas with < 5% CPU — under-utilized, consider removing
- NAT Gateway data transfer — often > $100/month from S3 requests that should go via VPC endpoint
- Elastic IPs not attached — $0.005/hr adds up across dev environments
- CloudWatch logs with 5-year retention — most logs useful for < 30 days
- Load balancers with no targets — forgotten non-prod ALBs from abandoned services
Database Query Cost Optimization
Our RDS costs are $2,400/month. The instance type seems large for our traffic.
Analyze what's causing the load.
-- Find queries consuming the most resources (requires pg_stat_statements)
SELECT
LEFT(query, 100) AS query_preview,
calls,
total_exec_time / 1000 AS total_seconds,
mean_exec_time::int AS avg_ms,
rows / calls AS avg_rows,
100 * total_exec_time / SUM(total_exec_time) OVER () AS pct_total_time
FROM pg_stat_statements
WHERE calls > 100
ORDER BY total_exec_time DESC
LIMIT 20;
These are the top 10 expensive queries from pg_stat_statements (paste output).
For each query, explain:
1. What it's doing
2. Why it's slow (likely cause)
3. How to fix it (index, rewrite, or caching)
Common query patterns that inflate costs:
-- N+1: loading orders then fetching user for each one
-- Fix: JOIN or subquery
SELECT * FROM orders WHERE status = 'active';
-- Then: for each order: SELECT * FROM users WHERE id = {order.user_id}
-- Fixed: single query
SELECT o.*, u.name, u.email
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'active';
-- Missing index on foreign key (full scan per user)
EXPLAIN SELECT * FROM order_items WHERE order_id = $1;
-- Seq Scan → add: CREATE INDEX CONCURRENTLY ON order_items(order_id);
-- Large result set returning to app then filtered in code
SELECT * FROM events WHERE user_id = $1; -- Returns 50K rows
-- Fix: push filter to DB
SELECT * FROM events WHERE user_id = $1 AND created_at > NOW() - INTERVAL '30d';
EC2/RDS Rightsizing
Here are our EC2 instances and their 2-week CloudWatch metrics.
Which ones should be downsized and by how much?
// scripts/rightsizing-analysis.ts
// Analyze CloudWatch metrics and recommend instance type changes
import { CloudWatchClient, GetMetricStatisticsCommand } from '@aws-sdk/client-cloudwatch';
const cw = new CloudWatchClient({ region: process.env.AWS_REGION });
interface InstanceMetrics {
instanceId: string;
instanceType: string;
avgCpuPercent: number;
maxCpuPercent: number;
avgMemoryPercent?: number; // Requires CloudWatch agent
currentMonthlyCost: number;
}
async function analyzeInstance(instanceId: string, instanceType: string): Promise<InstanceMetrics> {
const endTime = new Date();
const startTime = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000); // 14 days
const cpuData = await cw.send(new GetMetricStatisticsCommand({
Namespace: 'AWS/EC2',
MetricName: 'CPUUtilization',
Dimensions: [{ Name: 'InstanceId', Value: instanceId }],
StartTime: startTime,
EndTime: endTime,
Period: 3600, // 1-hour periods
Statistics: ['Average', 'Maximum'],
}));
const datapoints = cpuData.Datapoints ?? [];
const avgCpu = datapoints.reduce((sum, d) => sum + (d.Average ?? 0), 0) / datapoints.length;
const maxCpu = Math.max(...datapoints.map(d => d.Maximum ?? 0));
return {
instanceId,
instanceType,
avgCpuPercent: Math.round(avgCpu),
maxCpuPercent: Math.round(maxCpu),
currentMonthlyCost: getInstanceMonthlyCost(instanceType),
};
}
function recommendDownsize(metrics: InstanceMetrics): string | null {
const { avgCpuPercent, maxCpuPercent, instanceType } = metrics;
// Downsize candidates: avg < 10% AND max < 30% (leaving headroom)
if (avgCpuPercent < 10 && maxCpuPercent < 30) {
const nextSmaller = getNextSmallerInstanceType(instanceType);
if (nextSmaller) {
const savings = metrics.currentMonthlyCost - getInstanceMonthlyCost(nextSmaller);
return `Downsize to ${nextSmaller} — save $${savings.toFixed(0)}/month (avg: ${avgCpuPercent}%, max: ${maxCpuPercent}%)`;
}
}
return null;
}
CDN Cost Optimization
Our CloudFront costs are high. 85% of our traffic is images.
Help me reduce origin requests and improve cache hit ratio.
// src/middleware/cache-headers.ts — Optimize CDN cache behavior
export function setCacheHeaders(res: Response, opts: {
type: 'static' | 'dynamic' | 'api',
maxAge?: number,
}) {
switch (opts.type) {
case 'static':
// Immutable assets (JS, CSS with content hash in filename)
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
break;
case 'dynamic':
// Pages that change but have known TTL
res.setHeader('Cache-Control', `public, s-maxage=${opts.maxAge ?? 300}, stale-while-revalidate=60`);
// Cache-Tag for targeted invalidation (CloudFront)
res.setHeader('Cache-Tag', `page`);
break;
case 'api':
// API responses: short CDN cache, respect downstream cache
res.setHeader('Cache-Control', 'public, s-maxage=30, stale-while-revalidate=10, no-cache');
break;
}
}
# CloudFront cache behavior for images — maximize hit ratio
CacheBehaviors:
- PathPattern: "/images/*"
CachePolicyId: !Ref ImagesCachePolicy
Compress: true
ViewerProtocolPolicy: redirect-to-https
# Create optimized cache policy
- ImagesCachePolicy:
Type: AWS::CloudFront::CachePolicy
Properties:
CachePolicyConfig:
DefaultTTL: 86400 # 1 day default
MaxTTL: 31536000 # 1 year max
MinTTL: 3600 # 1 hour min
ParametersInCacheKeyAndForwardedToOrigin:
EnableAcceptEncodingGzip: true
EnableAcceptEncodingBrotli: true
HeadersConfig:
HeaderBehavior: none # Don't vary on headers
QueryStringsConfig:
QueryStringBehavior: whitelist
QueryStrings:
- w # Only vary cache on width param (for image resizing)
- q # Quality param
Automated Cost Alerting
// scripts/cost-alerts.ts — Daily cost monitoring with anomaly detection
import { CostExplorerClient, GetCostAndUsageCommand } from '@aws-sdk/client-cost-explorer';
const ce = new CostExplorerClient({ region: 'us-east-1' });
async function checkDailyCostAnomaly() {
const today = new Date().toISOString().split('T')[0];
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
const lastWeekSameDay = new Date(Date.now() - 7 * 86400000).toISOString().split('T')[0];
const [todayCost, lastWeekCost] = await Promise.all([
getDailyCost(today, yesterday), // Yesterday's full day
getDailyCost(lastWeekSameDay, yesterday.replace(/\d{4}-\d{2}-\d{2}/, lastWeekSameDay)),
]);
const changePercent = ((todayCost - lastWeekCost) / lastWeekCost) * 100;
if (changePercent > 20) { // > 20% week-over-week spike
await sendSlackAlert({
message: `⚠️ AWS cost anomaly: yesterday was $${todayCost.toFixed(2)} (${changePercent.toFixed(0)}% vs same day last week: $${lastWeekCost.toFixed(2)}). Investigate: AWS Cost Explorer`,
channel: '#ops-alerts',
});
}
}
For database query performance that directly impacts RDS costs, see the advanced PostgreSQL guide. For Kubernetes resource requests/limits setting that prevent over-provisioning, see the Kubernetes guide. The Claude Skills 360 bundle includes infrastructure skill sets for cloud cost analysis, rightsizing automation, and FinOps workflows. Start with the free tier to try cost optimization code generation.