DEV Community

Cloud Frontier
Cloud Frontier

Posted on

Cutting Cloud Costs With a Few Habits

I've watched cloud bills creep up on three different teams now. Every time, the fix wasn't some clever architecture rewrite. It was a handful of habits that took maybe an hour a week to maintain. Here's what actually moved the needle for me.

Tag everything, or you can't fix anything

You can't cut what you can't attribute. Before touching anything else, I make sure every resource carries at least team, env, and service tags. On AWS you can enforce this with a policy, but honestly a weekly script that lists untagged resources is enough to keep people honest.

# AWS: find running instances missing a Team tag
aws ec2 describe-instances \
  --query 'Reservations[].Instances[?State.Name==`running`].[InstanceId,Tags]' \
  --output json | jq -r '.[] | select(.[1] | map(.Key) | index("Team") | not) | .[0]'
Enter fullscreen mode Exit fullscreen mode

Once the bill is grouped by team, the conversation changes. Nobody argues about a cost they can see next to their own name.

Right-size before you optimize

Most overprovisioning I've seen isn't malicious, it's defensive. Someone picked a bigger instance "just in case" and it stuck. I check CPU and memory utilization over 30 days, not 5 minutes. If p95 CPU is under 20% for a month, I drop a size and watch it for a week.

The same logic applies to databases. Managed DB instances are often the single biggest line item, and they're frequently sized for a traffic peak that happened once.

Kill idle and orphaned resources

This is the boring one that pays every month:

  • Unattached EBS volumes and old snapshots
  • Idle load balancers with no targets
  • Elastic IPs not attached to anything
  • Dev and staging environments running nights and weekends

A simple scheduled Lambda that stops non-prod instances outside working hours can cut a meaningful chunk of compute. I use a tag like Schedule=office-hours and let the automation handle the rest.

import boto3

ec2 = boto3.client("ec2")

def handler(event, context):
    action = event.get("action", "stop")
    filters = [
        {"Name": "tag:Schedule", "Values": ["office-hours"]},
        {"Name": "instance-state-name", "Values": ["running"]},
    ]
    ids = [i["InstanceId"] for r in ec2.describe_instances(Filters=filters)["Reservations"] for i in r["Instances"]]
    if not ids:
        return
    if action == "stop":
        ec2.stop_instances(InstanceIds=ids)
    else:
        ec2.start_instances(InstanceIds=ids)
Enter fullscreen mode Exit fullscreen mode

Wire it to a cron rule for 7pm and 7am on weekdays. That's it.

Watch storage tiers and lifecycle rules

Object storage quietly accumulates. Logs, backups, and old artifacts sit in the hot tier forever because nobody set a lifecycle policy. A basic rule that moves objects to infrequent access after 30 days and deletes them after 180 is a two-minute change with a long tail of savings.

I set these at the bucket level the day the bucket is created. Retrofitting is harder because you have to reason about what's safe to delete.

Make the bill visible

Dashboards don't save money by themselves, but they change behavior. I post a weekly cost summary in the team channel, grouped by service, with the delta from last week. When a number jumps 40%, someone usually knows why before I even ask.

I also set budget alerts. Not at the monthly total, but at 80% of the expected spend so there's time to react.

The habit that matters most

Treat cost like a code review concern. When a PR adds a new instance, a new bucket, or a bigger database, someone asks what it costs and whether the smallest viable option was chosen. That single habit catches most of the waste before it ever shows up on an invoice.

None of this requires a FinOps team. It requires tags, a couple of scheduled jobs, and someone glancing at the bill once a week.

Top comments (0)