<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rajesh Medampudi</title>
    <description>The latest articles on DEV Community by Rajesh Medampudi (@medampudi).</description>
    <link>https://dev.to/medampudi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187600%2F5aab1531-1699-4511-8ad5-0c7258e6c14e.jpeg</url>
      <title>DEV Community: Rajesh Medampudi</title>
      <link>https://dev.to/medampudi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/medampudi"/>
    <language>en</language>
    <item>
      <title>Why I Rewrote Four Services in Go</title>
      <dc:creator>Rajesh Medampudi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 15:51:52 +0000</pubDate>
      <link>https://dev.to/medampudi/why-i-rewrote-four-services-in-go-2i0p</link>
      <guid>https://dev.to/medampudi/why-i-rewrote-four-services-in-go-2i0p</guid>
      <description>&lt;p&gt;I had four small services. Each one was a Model Context Protocol adapter — a thin wrapper that lets an AI agent call out to some external thing. One talked to Replicate for image generation. One talked to a Nostr-friendly social poster. One was a Git-aware research helper. One was a Tavily-powered web search.&lt;/p&gt;

&lt;p&gt;They were all written in Python. They all ran on Knative on a small Kubernetes cluster. They all worked. And they were all just &lt;em&gt;slightly&lt;/em&gt; too slow to use.&lt;/p&gt;

&lt;p&gt;A six-second cold start is fine for nothing. It is the precisely wrong amount of time — slow enough to be noticed, fast enough to feel almost loaded. An AI agent waiting six seconds for a single tool call does not know it is waiting for a cold start; it just knows the tool is sluggish. The user does not know either. The user just thinks the agent is broken.&lt;/p&gt;

&lt;p&gt;And six seconds was a good day. Some of the services took longer.&lt;/p&gt;

&lt;p&gt;So I rewrote them in Go. This is what that cost me, and what the measurements actually were before and after.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual problem
&lt;/h2&gt;

&lt;p&gt;Cold starts on serverless platforms are an old problem with a well-known shape. The platform spins your container up only when traffic arrives, so the first request after an idle period pays the full startup tax — image pull (or warm cache hit), container start, language runtime initialisation, application bootstrap.&lt;/p&gt;

&lt;p&gt;For Python, application bootstrap is where the bill arrives. The interpreter has to start. &lt;code&gt;import&lt;/code&gt; statements run. The dependency tree gets walked. If you have ever wondered why a &lt;em&gt;hello world&lt;/em&gt; Flask app feels so much heavier than a &lt;em&gt;hello world&lt;/em&gt; Go binary, this is why. Python is doing real work before your code runs. Go has already started.&lt;/p&gt;

&lt;p&gt;On a small Kubernetes cluster — small as in &lt;em&gt;I am paying for it personally&lt;/em&gt; — you do not keep a fleet of warm replicas around. You scale-to-zero. You scale-to-zero because that is the entire point of using serverless on small infrastructure. The trade-off is that every idle service eats a cold start the next time it is invoked.&lt;/p&gt;

&lt;p&gt;For my four MCPs, &lt;em&gt;next time it is invoked&lt;/em&gt; was approximately every time an agent decided to use them. Which was constantly. Which meant cold starts were not the rare edge case. They were the common case.&lt;/p&gt;

&lt;p&gt;The measured cold-start latency on each Python MCP, taken from production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;postiz-mcp&lt;/code&gt; — &lt;strong&gt;5.9 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git-mcp&lt;/code&gt; — &lt;strong&gt;4.9 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;research-mcp&lt;/code&gt; — &lt;strong&gt;5.4 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;image-mcp&lt;/code&gt; — &lt;strong&gt;7.2 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For comparison, the one MCP I had already written in Go (&lt;code&gt;clickup-mcp&lt;/code&gt;) was cold-starting in &lt;strong&gt;200 to 400 milliseconds&lt;/strong&gt;. An order of magnitude faster, on the same cluster, in the same Knative configuration.&lt;/p&gt;

&lt;p&gt;But the headline number was the &lt;em&gt;workflow chain&lt;/em&gt;. A real agent run — drafting a blog post — hits four MCPs sequentially in its draft phase. On Python, that meant &lt;strong&gt;20 to 25 seconds&lt;/strong&gt; of cumulative cold-start latency every time the chain ran cold. On Go, the same chain came down to &lt;strong&gt;roughly 2 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Twenty seconds of staring at a loading spinner, every time, in steady state. That was the ceiling I was bumping into.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rewrite
&lt;/h2&gt;

&lt;p&gt;I would like to tell you the rewrite was elegant. It was not. It was four small slogs, in a row, on weekends, with the same set of decisions made four times in a row because I was too lazy to extract the boilerplate properly until service three.&lt;/p&gt;

&lt;p&gt;The actual work, per service, looked like this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting up the project.&lt;/strong&gt; Pick a Go module name, decide on a directory structure, pick an HTTP framework or just use the standard library, pick a logger. Repeat per service. (I eventually settled on &lt;code&gt;net/http&lt;/code&gt; plus &lt;code&gt;slog&lt;/code&gt;. The number of choices Go gives you for stdlib HTTP is &lt;em&gt;zero&lt;/em&gt;, and that is why it is faster than the equivalent in any other ecosystem.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Translating the SDK calls.&lt;/strong&gt; The Python services used native Python SDKs for Replicate, the Nostr toolkit, and so on. Go either had a vendor SDK, a community one, or — in two cases — nothing usable. In those two cases I just hand-rolled the HTTP requests because the APIs were small enough. This took less time than I expected. Most modern APIs are thin wrappers over REST and JSON, which is exactly what &lt;code&gt;net/http&lt;/code&gt; and &lt;code&gt;encoding/json&lt;/code&gt; are built for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Translating the state.&lt;/strong&gt; Two of the services held a small amount of in-memory state — caches, mostly. Python made this trivial; Go made me think about it. In both cases the right answer was &lt;em&gt;use &lt;code&gt;sync.Map&lt;/code&gt; and stop overengineering&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Containerising.&lt;/strong&gt; This is where the win started becoming visible. A multi-stage Go Dockerfile (&lt;code&gt;golang:1.25-alpine&lt;/code&gt; builder going to a &lt;code&gt;FROM scratch&lt;/code&gt; final) produces a static binary in an image of &lt;strong&gt;about 20 megabytes&lt;/strong&gt;. The Python images had been &lt;strong&gt;about 300 megabytes&lt;/strong&gt; with all their dependencies. Pull times went from "perceptible" to "negligible." This is a real cold-start contribution that has nothing to do with language runtime — it is image-pull latency, which is dominated by image size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wiring observability.&lt;/strong&gt; The Python services had structured logging via Loguru. The Go services got &lt;code&gt;slog&lt;/code&gt; with the same JSON output. Same dashboards still worked. Same alerts still worked. Nobody in the observability stack knew or cared that the language had changed, which is a very nice property for a rewrite.&lt;/p&gt;

&lt;p&gt;The total time, across four services and a lot of starting-and-stopping, was something like three weekends. None of them were full weekends. None of them were free of context-switching to other work. So call it twenty hours, generously.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unexpected wins
&lt;/h2&gt;

&lt;p&gt;A few things landed in my lap that I had not planned for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No more Python image dance.&lt;/strong&gt; Building Python container images involves a lot of dancing — the right base image, the right Python version, the right system packages, the right pip cache strategy, the right &lt;code&gt;--no-deps&lt;/code&gt; if you have already got a vendored wheel directory. Half the Dockerfiles in my old repo were Python wheel-and-glue. The Go equivalents are six lines and they all look the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single binary deploys.&lt;/strong&gt; When the only artefact your service produces is a compiled binary, deployment becomes a &lt;code&gt;COPY&lt;/code&gt; instruction in a &lt;code&gt;FROM scratch&lt;/code&gt; image. There is no virtualenv. There is no missing system library. There is no "works on my laptop, fails on the cluster" because you have shipped exactly the same bytes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Easier to read in production.&lt;/strong&gt; Go's lack of magic — its terrible-by-design lack of expressiveness — turned out to be a virtue when I was on-call at two in the morning trying to figure out why an MCP was throwing 500s. There is one way to do error handling. There is one way to do concurrency. There is one way to read a JSON body. The code is &lt;em&gt;boring&lt;/em&gt;, and the boring code was easier to debug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smaller blast radius for dependency updates.&lt;/strong&gt; Go modules with &lt;code&gt;go.sum&lt;/code&gt; produce reproducible builds. A &lt;code&gt;go mod tidy&lt;/code&gt; followed by a build verifies the entire dependency graph. When you update a Python package, you find out at runtime whether it was a breaking change. When you update a Go package, you find out at compile time. This is not a small difference at two in the morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unexpected pains
&lt;/h2&gt;

&lt;p&gt;Three things hurt that I had underestimated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boilerplate.&lt;/strong&gt; Go has a verbose-by-design philosophy. Where Python lets me read a JSON request body in two lines, Go takes six. Where Python has list comprehensions, Go has &lt;code&gt;for&lt;/code&gt; loops. Where Python has decorators, Go has wrapper functions. Multiply this by the surface area of four services and you write a &lt;em&gt;lot&lt;/em&gt; of code that, in Python, was implicit. I learned to like this. But I had to learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error handling discipline.&lt;/strong&gt; &lt;code&gt;if err != nil&lt;/code&gt; is the most-mocked line in Go and it deserves the mockery — until you spend an afternoon debugging a Python service that swallowed an exception three layers deep. Go's &lt;em&gt;every error is in your face&lt;/em&gt; model is exhausting to write and reassuring to read. I now know exactly what every one of my services does when &lt;code&gt;replicate.Run()&lt;/code&gt; returns an error. I did not know that for the Python equivalents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The library gap on niche packages.&lt;/strong&gt; For Replicate and Tavily, the Go SDKs were either community-maintained or non-existent, and I rolled my own HTTP. That is fine if the API is small. It is painful if the API is large or undocumented. One of my services lost a weekend to &lt;em&gt;what does this API actually do when you pass it an empty string for a required field&lt;/em&gt; research that I would not have had to do if a vendor-maintained SDK existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;These are the actual measurements from before and after — taken on the same cluster, the same Knative configuration, the same workload.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cold-start latency&lt;/strong&gt; — Python services were measuring 4.9 to 7.2 seconds (about 6 seconds average across the four). The Go rewrites came in at 200 to 400 milliseconds. Roughly &lt;strong&gt;20× faster&lt;/strong&gt; at the per-call level.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory at idle&lt;/strong&gt; — Python services were sitting at about 80 MiB each. Go binaries sit at about 15 MiB. Across five services, that is the difference between &lt;strong&gt;400 MiB always reserved&lt;/strong&gt; and &lt;strong&gt;75 MiB always reserved&lt;/strong&gt; — and on a small cluster with limited CPU per VM, this is the kind of footprint that matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image sizes&lt;/strong&gt; — about 300 MB Python images became about 20 MB Go images. &lt;strong&gt;15× smaller&lt;/strong&gt;, with proportional improvements to image-pull latency and registry storage churn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The headline number — workflow chain latency.&lt;/strong&gt; A real agent run that chains four MCPs sequentially in its draft phase used to eat &lt;strong&gt;20–25 seconds&lt;/strong&gt; of cumulative cold-start tax every time the chain ran cold. After the rewrite, the same chain takes &lt;strong&gt;about 2 seconds end-to-end&lt;/strong&gt;. That is the user-facing win, and it is the one I actually care about. The rest is plumbing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agents, which were the actual customers, started working better. Tool-call chains that had felt sluggish now feel responsive. That is the only metric I deeply care about. Everything else is vanity.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should not do this
&lt;/h2&gt;

&lt;p&gt;You should not rewrite a Python service in Go if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The service is fast enough.&lt;/strong&gt; If your cold start is already under a second and you are not running on a small cluster, this is not your problem. Do not make it your problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You are doing data science.&lt;/strong&gt; Python's library ecosystem for ML, statistics, image processing, anything-with-numpy is incomparable. Go is not where your model-serving service should live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You are using a heavy framework that Go does not have.&lt;/strong&gt; If your Python service is built on Django or some specific async framework with deep ecosystem dependencies, the Go port is not a port — it is a rewrite. Different math.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You are a Python team.&lt;/strong&gt; If you do not already have Go in production, the operational cost of introducing a new language for one service is not worth the latency win on that service. Pick your battles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I had four small adapters with simple request-response shapes, no heavy library deps, and a latency floor that mattered. That is the exact shape Go was good for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;If I had to do this over, I would extract the boilerplate before the second service, not the third. Two-thirds of each MCP is the same — HTTP setup, JSON request-and-response handling, MCP protocol scaffolding, error envelopes, structured logging. By service three I had a small internal library that did all of that. By service four it was a copy-paste. Services one and two had a lot of code I would have happily deleted in retrospect.&lt;/p&gt;

&lt;p&gt;I would also keep at least one Python service around as a comparison baseline, so when someone asks &lt;em&gt;"could you not have just optimised the Python,"&lt;/em&gt; I have a same-shape service to point to. (I can confidently tell you the answer is no, not at the cold-start level, but I cannot &lt;em&gt;show&lt;/em&gt; you, which is annoying.)&lt;/p&gt;

&lt;p&gt;The bigger meta-lesson — small services on a small cluster are a different optimisation problem than big services on big infrastructure. On EKS with a fleet of warm replicas, none of this matters; keep your Python. On a six-node cluster scaling-to-zero between requests, every second of cold start is a second the user is staring at a loading spinner. Pick the language that respects the loading spinner.&lt;/p&gt;

&lt;p&gt;I respect the loading spinner now. The agents do too. The four services do not time out any more.&lt;/p&gt;

&lt;p&gt;That is the whole win, in one sentence.&lt;/p&gt;

</description>
      <category>go</category>
      <category>python</category>
      <category>performance</category>
      <category>backend</category>
    </item>
    <item>
      <title>Cloud Cost Management: Your Bill Is a Product Metric</title>
      <dc:creator>Rajesh Medampudi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 15:51:49 +0000</pubDate>
      <link>https://dev.to/medampudi/cloud-cost-management-your-bill-is-a-product-metric-l3h</link>
      <guid>https://dev.to/medampudi/cloud-cost-management-your-bill-is-a-product-metric-l3h</guid>
      <description>&lt;p&gt;The cloud bill is the only number in most companies that nobody on the team owns until it's already a problem.&lt;/p&gt;

&lt;p&gt;Engineering owns latency. It owns error rates, p99, uptime, the whole observability wall. Finance owns the invoice. And between those two ownerships there's a gap wide enough to drive a fifth to a third of your cloud spend straight into a wall — which, across the industry, is roughly what happens. The fix isn't a smarter spreadsheet at month-end. Real cloud cost management isn't an accounting function at all — the fix is to stop treating the bill as accounting and start treating it as a product metric: cost per request, cost per tenant, cost per feature, sitting on the same dashboard as latency and error rate, owned by the same people who own those numbers.&lt;/p&gt;

&lt;p&gt;That's the whole argument. The rest of this post is why it's true and how it's done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bill is a lagging accounting artifact, and that's the bug
&lt;/h2&gt;

&lt;p&gt;Here's how cloud cost is treated almost everywhere. A bill arrives. Someone in finance reconciles it against a budget. If it's higher than expected, a thread gets opened, an engineer gets pulled in, and everyone spends a week spelunking through Cost Explorer trying to reconstruct &lt;em&gt;why&lt;/em&gt; a number that's already been spent is what it is. Then it happens again next month.&lt;/p&gt;

&lt;p&gt;Every part of that loop is broken. The signal arrives weeks after the decision that caused it. The person reading the signal can't act on it. The person who can act on it never sees it. And the unit of measurement — total dollars — tells you nothing about whether the spend was &lt;em&gt;good&lt;/em&gt;. A bill that doubled because you doubled revenue is a triumph. A bill that doubled because someone left a debug log streaming to an expensive tier is a fire. Total dollars can't tell those two apart. They look identical on the invoice.&lt;/p&gt;

&lt;p&gt;This is the same mistake we'd never make with any other production signal. Nobody reviews latency once a month from a PDF. Nobody waits for finance to tell engineering that p99 regressed. We put it on a graph, we attach it to the deploy that moved it, we alert when it crosses a line. Cost is the one production signal we still run like a 1990s expense report. The waste isn't an accident — it's the structural consequence of measuring the wrong thing, late, in front of the wrong people.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes when cost becomes a unit metric
&lt;/h2&gt;

&lt;p&gt;The unlock is dividing. Instead of asking "what did we spend," you ask "what did we spend &lt;em&gt;per unit of the thing the business actually sells&lt;/em&gt;." Total infra cost over the number of requests gives you cost per request. Over active tenants, cost per tenant. Over inferences, if you're running models, cost per inference. The FinOps Foundation — the industry body that codified this practice — calls this capability Unit Economics, and defines it plainly: it "brings together what an organization spends on technology and the value that technology spending creates" (&lt;a href="https://www.finops.org/framework/capabilities/unit-economics/" rel="noopener noreferrer"&gt;FinOps Foundation, Unit Economics capability&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The arithmetic is trivial. The shift it forces is not.&lt;/p&gt;

&lt;p&gt;Once cost is &lt;em&gt;per unit&lt;/em&gt;, a rising bill stops being alarming by default. If cost per request is flat and the bill is up, you grew — celebrate. If the bill is flat but cost per request is climbing, you have a real problem hiding behind a calm-looking invoice, and you found it before finance did. The unit metric separates the two failure modes the raw total fused together. That separation is the entire point.&lt;/p&gt;

&lt;p&gt;The Foundation splits these into two useful buckets. &lt;strong&gt;Resource-efficiency metrics&lt;/strong&gt; — cost per GB stored, cost per vCPU, cost per GB transferred, cost per token — tell engineers whether the machinery is tight. &lt;strong&gt;Business metrics&lt;/strong&gt; — cost per tenant, cost per transaction, cost to serve, cost per case resolved — tell the business whether the product makes money at the unit level (&lt;a href="https://www.finops.org/framework/capabilities/unit-economics/" rel="noopener noreferrer"&gt;FinOps Foundation, Unit Economics capability&lt;/a&gt;). You want both. The first tells you &lt;em&gt;how&lt;/em&gt; you're wasting; the second tells you &lt;em&gt;whether it matters&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I'll be honest about the limit here: getting a clean cost-per-tenant number in a genuinely multi-tenant system, where tenants share clusters, share databases, share a NAT gateway, is hard. Allocation is the unglamorous, real work of this whole discipline — tagging, cost-allocation keys, splitting shared infrastructure on a defensible ratio. Anyone who tells you cost-per-tenant falls out of the bill for free hasn't built it. But "hard to attribute perfectly" is not "not worth approximating." An 80%-right cost-per-tenant on a dashboard beats a 100%-right total invoice nobody reads. (The hands-on version of finding where the money actually goes is &lt;a href="https://dev.to/blog/audit-50k-aws-bill"&gt;what I'd audit first on a $50K AWS bill&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Put it on the dashboard, or it isn't real
&lt;/h2&gt;

&lt;p&gt;A metric that lives in a monthly finance review is not a metric the people who move it ever see. This is the part most "we do FinOps" claims quietly skip.&lt;/p&gt;

&lt;p&gt;Cost per request belongs on the same Grafana board as latency and error rate — same screen, same refresh, looked at by the same on-call engineer at the same moment. Not because engineers should obsess over money, but because the cost of a code path is a property of that code path, exactly like its latency. An engineer who can see that a new endpoint costs 4x per call what the old one did will fix it in the pull request, while the context is hot, for the price of a code review. The same regression caught six weeks later in a finance reconciliation costs a forensic investigation, a context-switch back into code nobody remembers, and a meeting. Same bug. Two orders of magnitude difference in what it costs to fix, decided entirely by &lt;em&gt;when and where the number was visible&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The FinOps framework names this the Inform phase — make cost, usage, and efficiency data visible and timely before you try to optimise anything (&lt;a href="https://www.finops.org/framework/phases/" rel="noopener noreferrer"&gt;FinOps Foundation, FinOps Phases&lt;/a&gt;). Visibility first. You cannot optimise a number nobody can see, and you cannot create ownership of a number that only appears in someone else's department's PDF.&lt;/p&gt;

&lt;h2&gt;
  
  
  Showback before chargeback
&lt;/h2&gt;

&lt;p&gt;Once cost is per-unit and visible, the next question is who carries it. Two models, and the order you adopt them matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Showback&lt;/strong&gt; shows each team what its slice of the bill is — without billing them for it. &lt;strong&gt;Chargeback&lt;/strong&gt; actually moves the cost onto the team's own budget. Most engineering orgs that reach for chargeback first end up in a turf war: teams dispute the allocation, argue the shared-infra split is unfair, and the energy that should go into &lt;em&gt;reducing&lt;/em&gt; cost goes into &lt;em&gt;contesting&lt;/em&gt; it instead.&lt;/p&gt;

&lt;p&gt;Showback first. Let teams see their number for a quarter or two with no money attached. Visibility alone moves behaviour, because most over-spend isn't malice — it's invisibility. The team running the over-provisioned cluster usually doesn't know it's over-provisioned; they've just never seen the number isolated to them. Show it, and a meaningful fraction self-corrects before anyone has to enforce anything. Chargeback is the tool you reach for &lt;em&gt;after&lt;/em&gt; showback has done the easy 60%, when you need accountability with teeth on the stubborn remainder. Lead with the budget transfer and you'll spend your political capital on the dispute instead of the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is a culture metric, not a tooling metric
&lt;/h2&gt;

&lt;p&gt;You can buy every cost tool on the market and still waste a quarter of your spend, because the tools surface the number and the &lt;em&gt;culture&lt;/em&gt; decides whether anyone acts on it. FinOps, in the Foundation's own framing, is "a cultural practice" — collaboration between engineering, finance, and product, with everyone taking ownership of their own technology usage (&lt;a href="https://www.finops.org/framework/" rel="noopener noreferrer"&gt;FinOps Foundation, Framework Overview&lt;/a&gt;). The tool is the easy part. The hard part is making cost a thing engineers are &lt;em&gt;proud&lt;/em&gt; to have tight, the way they're proud of a clean p99.&lt;/p&gt;

&lt;p&gt;And the stakes scale with the bill. Globally, Flexera's 2025 State of the Cloud report found 84% of organisations name managing cloud spend as their top cloud challenge (&lt;a href="https://www.flexera.com/about-us/press-center/new-flexera-report-finds-84-percent-of-organizations-struggle-to-manage-cloud-spend" rel="noopener noreferrer"&gt;vendor: Flexera, 2025 State of the Cloud press release&lt;/a&gt;), and the long-running industry estimate of wasted spend sits at roughly a fifth to a third — a range that's barely moved in years. That's not a rounding error. On a serious cloud bill, a quarter of it evaporating is the difference between a profitable product and one that's quietly subsidising its own infrastructure. The companies that fix it aren't the ones with the fanciest dashboards. They're the ones where the engineer who wrote the expensive query saw the number, owned it, and shipped the fix in the same afternoon — because the cost was sitting right there next to the latency, where it always should have been.&lt;/p&gt;

&lt;p&gt;Treat the bill as accounting and you'll reconcile it forever. Treat it as a product metric and you'll &lt;em&gt;engineer&lt;/em&gt; it — which is the only thing that's ever actually moved the number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;FinOps Foundation — Unit Economics capability (definition; resource-efficiency vs business unit metrics; cost-per-X list). &lt;a href="https://www.finops.org/framework/capabilities/unit-economics/" rel="noopener noreferrer"&gt;https://www.finops.org/framework/capabilities/unit-economics/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;FinOps Foundation — Framework Overview (FinOps as a cultural practice; collaboration between engineering, finance, business; ownership principle). &lt;a href="https://www.finops.org/framework/" rel="noopener noreferrer"&gt;https://www.finops.org/framework/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;FinOps Foundation — FinOps Phases (Inform / Optimize / Operate; Inform = make cost/usage/efficiency data visible and timely). &lt;a href="https://www.finops.org/framework/phases/" rel="noopener noreferrer"&gt;https://www.finops.org/framework/phases/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;FinOps Foundation — Introduction to Cloud Unit Economics working group (unit cost = total infra cost ÷ units produced; cost per request, per tenant, per inference). &lt;a href="https://www.finops.org/wg/introduction-cloud-unit-economics/" rel="noopener noreferrer"&gt;https://www.finops.org/wg/introduction-cloud-unit-economics/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flexera — 2025 State of the Cloud, press release (84% name managing cloud spend as top challenge). &lt;a href="https://www.flexera.com/about-us/press-center/new-flexera-report-finds-84-percent-of-organizations-struggle-to-manage-cloud-spend" rel="noopener noreferrer"&gt;https://www.flexera.com/about-us/press-center/new-flexera-report-finds-84-percent-of-organizations-struggle-to-manage-cloud-spend&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>finops</category>
      <category>cloud</category>
      <category>costoptimization</category>
    </item>
    <item>
      <title>The S3 Cost Optimization Playbook</title>
      <dc:creator>Rajesh Medampudi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 15:51:46 +0000</pubDate>
      <link>https://dev.to/medampudi/the-s3-cost-optimization-playbook-a48</link>
      <guid>https://dev.to/medampudi/the-s3-cost-optimization-playbook-a48</guid>
      <description>&lt;p&gt;Most S3 bills are wrong, and the fix takes an afternoon. The data sits in the most expensive class AWS offers (S3 Standard, $0.023/GB-month), nobody set a lifecycle policy, incomplete multipart uploads are silently billing for storage you can't even see in the console, and every byte your EC2 fleet pulls from S3 is routed out through a NAT Gateway when a free VPC Gateway Endpoint would do the same job for $0. None of this needs an architecture rewrite. It needs a checklist run in the right order.&lt;/p&gt;

&lt;p&gt;Here is the order. The savings depend entirely on your access pattern — I will not promise you a number I can't see — but the mistakes below are so common that the question is usually &lt;em&gt;how much&lt;/em&gt;, not &lt;em&gt;whether&lt;/em&gt;. One number is just arithmetic: cold data that moves from S3 Standard ($0.023/GB-month) to Glacier Deep Archive ($0.00099/GB-month) drops about 96% on the storage line for those bytes, and on an &lt;a href="https://dev.to/blog/observability-cost-kill"&gt;observability platform I ran&lt;/a&gt; — logs aged past 90 days into Deep Archive — that is exactly the lever that did the work. S3 cost optimization is the same boring discipline as the rest of the bill: see it, then decide what each byte should actually cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, see the bill before you touch it
&lt;/h2&gt;

&lt;p&gt;You cannot optimize what you cannot measure, and S3's default billing view tells you nearly nothing useful. Turn on &lt;strong&gt;S3 Storage Lens&lt;/strong&gt; before anything else. The free tier gives you 62 metrics at the bucket level with 14 days of history, and crucially it includes cost-optimization metrics out of the box — including "Incomplete multipart upload bytes greater than 7 days old," which is the single most common source of money disappearing into storage nobody knows exists (&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage_lens_basics_metrics_recommendations.html" rel="noopener noreferrer"&gt;AWS S3 Storage Lens docs&lt;/a&gt;, accessed 2026-06-18).&lt;/p&gt;

&lt;p&gt;Storage Lens free metrics answer the three questions that decide everything that follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which buckets hold the most bytes?&lt;/li&gt;
&lt;li&gt;What storage class is that data sitting in right now?&lt;/li&gt;
&lt;li&gt;Where are the incomplete multipart uploads?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For deeper per-prefix analysis or a longer history, &lt;strong&gt;Advanced metrics&lt;/strong&gt; (15 months of data, recommendations) costs extra — worth it for a large estate, overkill for a single small account. Start free. Pay for advanced only once the free tier has told you the estate is big enough to justify it.&lt;/p&gt;

&lt;p&gt;Pair this with &lt;strong&gt;S3 Storage Class Analysis&lt;/strong&gt; on your busiest buckets. It watches access patterns and tells you which objects are candidates to move to Infrequent Access — so you set lifecycle thresholds from data, not from a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second, fix the silent leak: incomplete multipart uploads
&lt;/h2&gt;

&lt;p&gt;This is the one nobody finds on their own, so it goes near the top.&lt;/p&gt;

&lt;p&gt;When you upload a large object in parts and the upload fails partway — a dropped connection, a crashed job, an SDK that didn't clean up — the parts that &lt;em&gt;did&lt;/em&gt; land stay in the bucket. &lt;strong&gt;You are billed for that storage.&lt;/strong&gt; They do not appear in the normal object listing. They accumulate for years, and on a long-lived account the orphaned parts can add up to a meaningful fraction of the bill before anyone notices.&lt;/p&gt;

&lt;p&gt;The fix is one lifecycle rule, applied to every bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abort-incomplete-mpu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"AbortIncompleteMultipartUpload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"DaysAfterInitiation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS supports a lifecycle rule that stops multipart uploads not completed within a set number of days and deletes the orphaned parts — and it applies to both existing and future uploads (&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpu-abort-incomplete-mpu-lifecycle-config.html" rel="noopener noreferrer"&gt;AWS lifecycle config for incomplete MPU&lt;/a&gt;, accessed 2026-06-18). Seven days is the standard value; it is long enough that no legitimate in-flight upload gets killed, short enough that garbage doesn't pile up. And per AWS, removing incomplete multipart parts via lifecycle does &lt;em&gt;not&lt;/em&gt; trigger early-delete charges — so there is no downside.&lt;/p&gt;

&lt;p&gt;Set this rule on every bucket you own, today, before you do anything else. It is the cheapest win in the whole playbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third, get the data into the right storage class
&lt;/h2&gt;

&lt;p&gt;S3 storage classes span a roughly 23x price range, and the only thing separating them is access pattern. Here is the ladder, US East (N. Virginia), per GB-month:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;$/GB-month&lt;/th&gt;
&lt;th&gt;Min duration&lt;/th&gt;
&lt;th&gt;Min object size&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3 Standard&lt;/td&gt;
&lt;td&gt;$0.023&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;Active, frequently read data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard-IA&lt;/td&gt;
&lt;td&gt;$0.0125&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;td&gt;128 KB&lt;/td&gt;
&lt;td&gt;Read a few times a month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One Zone-IA&lt;/td&gt;
&lt;td&gt;~$0.01&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;td&gt;128 KB&lt;/td&gt;
&lt;td&gt;Reproducible IA data, single-AZ OK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glacier Instant Retrieval&lt;/td&gt;
&lt;td&gt;$0.004&lt;/td&gt;
&lt;td&gt;90 days&lt;/td&gt;
&lt;td&gt;128 KB&lt;/td&gt;
&lt;td&gt;Archive needing instant access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glacier Flexible Retrieval&lt;/td&gt;
&lt;td&gt;$0.0036&lt;/td&gt;
&lt;td&gt;90 days&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Archive, minutes-to-hours retrieval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glacier Deep Archive&lt;/td&gt;
&lt;td&gt;~$0.00099&lt;/td&gt;
&lt;td&gt;180 days&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Compliance, rarely-ever read&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Storage prices and the minimum-duration / minimum-size rules are from AWS primary docs (&lt;a href="https://aws.amazon.com/s3/storage-classes/" rel="noopener noreferrer"&gt;storage classes&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/glacier-storage-classes.html" rel="noopener noreferrer"&gt;Glacier classes&lt;/a&gt;, accessed 2026-06-18). Two rules carry most of the risk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimum storage duration.&lt;/strong&gt; Delete a Standard-IA object before 30 days and you still pay the full 30. Glacier Flexible and Instant bill a 90-day minimum; Deep Archive bills 180. Move data down the ladder only when it will actually sit there. Putting short-lived data in Glacier is a way to pay &lt;em&gt;more&lt;/em&gt;, not less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimum billable object size.&lt;/strong&gt; Standard-IA, One Zone-IA, and Glacier Instant bill every object as if it were at least 128 KB. A bucket of 10 KB thumbnails moved to Standard-IA gets billed at 128 KB each — you pay for 12x the bytes you store. &lt;strong&gt;Small objects stay in Standard.&lt;/strong&gt; This is the trap that quietly reverses the savings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decision rule is simple. Frequently read → Standard. Read occasionally, objects over 128 KB → Standard-IA. Rarely read but must be instant → Glacier Instant Retrieval. Archive you can wait minutes-to-hours for → Glacier Flexible. Compliance data you'll likely never read → Deep Archive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fourth, automate the transitions with lifecycle policies
&lt;/h2&gt;

&lt;p&gt;You do not move data by hand. You write a lifecycle policy and S3 does it on a schedule. A typical policy for log or backup data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tier-down-logs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"logs/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Transitions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"StorageClass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STANDARD_IA"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"StorageClass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GLACIER_IR"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"StorageClass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DEEP_ARCHIVE"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Expiration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2555&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to know before you ship a lifecycle policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transitions cost money per request.&lt;/strong&gt; Each lifecycle transition is a billed request, and the per-1,000 transition cost is higher for the colder classes (vendor: AWS S3 pricing — confirm the exact us-east-1 cents on the live page, the table is JS-rendered and these move). For a bucket of millions of tiny objects, the transition requests can cost more than the storage you save. This is the second reason small objects don't belong in IA — the move itself isn't worth it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiration is the most underused line.&lt;/strong&gt; If the data has a legal or practical end-of-life, set &lt;code&gt;Expiration&lt;/code&gt;. Storage you delete is storage you stop paying for forever. Most teams tier data down and then keep it for eternity because nobody wrote the expiry rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fifth, when access is unpredictable, use Intelligent-Tiering
&lt;/h2&gt;

&lt;p&gt;Lifecycle policies assume you &lt;em&gt;know&lt;/em&gt; the access pattern. When you don't — user uploads, a data lake, anything where some objects go cold and others stay hot unpredictably — &lt;strong&gt;S3 Intelligent-Tiering&lt;/strong&gt; is the right default. It monitors each object and moves it between tiers automatically: after 30 consecutive days with no access it drops to Infrequent Access (about 40% cheaper), and after 90 days to Archive Instant Access (about 68% cheaper), with no retrieval fee when an object gets hot again and is read (&lt;a href="https://aws.amazon.com/s3/storage-classes/intelligent-tiering/" rel="noopener noreferrer"&gt;AWS Intelligent-Tiering&lt;/a&gt;, accessed 2026-06-18).&lt;/p&gt;

&lt;p&gt;The cost is a monitoring-and-automation charge of $0.0025 per 1,000 objects per month (vendor: AWS, us-east-1). That math has one sharp edge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent-Tiering is wrong for billions of tiny objects.&lt;/strong&gt; The monitoring fee is per object, not per GB. A bucket of a billion small objects pays a monitoring charge that can dwarf any tiering savings. Per AWS, objects smaller than 128 KB are never auto-tiered and are always billed at the Frequent Access rate — but they can still rack up monitoring charges. For huge counts of small objects, a plain lifecycle policy (or just Standard) beats Intelligent-Tiering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rule of thumb: unknown access pattern + reasonably sized objects → Intelligent-Tiering and forget it. Known pattern, or billions of tiny objects → explicit lifecycle policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sixth, stop paying for retrieval and requests you didn't budget for
&lt;/h2&gt;

&lt;p&gt;Storage is the line everyone watches. Requests and retrievals are the lines that ambush you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval fees scale with how cold the class is.&lt;/strong&gt; Standard-IA and Glacier Instant charge a per-GB read fee; Glacier Flexible and Deep Archive charge a per-GB retrieval fee &lt;em&gt;plus&lt;/em&gt; a per-request fee, and Deep Archive's slowest retrieval tier is measured in hours, not seconds (vendor: AWS — confirm the exact per-GB cents and the retrieval-time SLA on the live S3 pricing and retrieval-options pages). The lesson: a class is only cheap if you read it as rarely as its design assumes. Putting frequently-read data in Glacier to save on storage and then paying retrieval on every read is the most expensive mistake in this whole document — the retrieval bill can exceed what Standard would have cost outright.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request pricing punishes chatty workloads.&lt;/strong&gt; S3 Standard GETs are cheap individually (~$0.0004 per 1,000) but a service doing millions of tiny GETs per minute turns "cheap" into a real line item. Batch, cache, and use CloudFront in front of read-heavy buckets so the requests never hit S3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before you tier anything down, ask the one question that governs the whole ladder: &lt;em&gt;how often is this actually read?&lt;/em&gt; If you don't know, that's what Storage Class Analysis and Intelligent-Tiering are for. If you guess wrong toward "cold," the retrieval fees make you pay for the guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seventh, the free win everyone leaves on the table: VPC Gateway Endpoints
&lt;/h2&gt;

&lt;p&gt;If your EC2, ECS, or Lambda workloads in a VPC talk to S3, check how that traffic leaves the VPC. By default, instances in a private subnet reach S3 through a &lt;strong&gt;NAT Gateway&lt;/strong&gt; — and NAT Gateway bills both an hourly charge and a per-GB data-processing charge on every byte. For a workload pulling terabytes from S3, that is a tax you are paying for nothing. (The full version of that problem is its own post: &lt;a href="https://dev.to/blog/aws-nat-gateway-hidden-tax"&gt;the NAT Gateway hidden tax&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;S3 Gateway VPC Endpoint&lt;/strong&gt; routes that traffic privately, and AWS charges &lt;strong&gt;nothing&lt;/strong&gt; for it — no hourly fee, no per-GB fee, and traffic to S3 in the same Region incurs no data transfer charge (&lt;a href="https://docs.aws.amazon.com/vpc/latest/privatelink/vpc-endpoints-s3.html" rel="noopener noreferrer"&gt;AWS Gateway endpoints for S3&lt;/a&gt;, accessed 2026-06-18). You add a route, and the same S3 traffic that was flowing through a metered NAT Gateway now flows free.&lt;/p&gt;

&lt;p&gt;One caveat worth stating honestly: Gateway endpoints work for traffic &lt;em&gt;originating inside the VPC in the same Region&lt;/em&gt;. They do not serve on-premises networks, peered VPCs in other Regions, or transit-gateway paths — those need an Interface endpoint, which &lt;em&gt;does&lt;/em&gt; cost money ($0.01/AZ/hour plus $0.01/GB). For the common case — instances in a VPC reading from S3 in the same Region — the Gateway endpoint is free and you should have created it on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order matters
&lt;/h2&gt;

&lt;p&gt;Run it top to bottom: turn on Storage Lens, kill incomplete multipart uploads, right-size storage classes, automate with lifecycle (or Intelligent-Tiering when the pattern is unknown), respect the retrieval and request fees, and add the free VPC Gateway Endpoint. Every step is reversible, none of it touches your application code, and the whole thing is an afternoon's work for savings that compound every month the data sits there.&lt;/p&gt;

&lt;p&gt;The reason this works is not cleverness. It is that S3's defaults are tuned for the most expensive, most available configuration, and almost nobody changes them. The money is sitting in plain sight. You just have to run the checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS — Object Storage Classes (storage prices, min sizes): &lt;a href="https://aws.amazon.com/s3/storage-classes/" rel="noopener noreferrer"&gt;https://aws.amazon.com/s3/storage-classes/&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;li&gt;AWS — Understanding S3 Glacier storage classes (min durations 90/90/180): &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/glacier-storage-classes.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/glacier-storage-classes.html&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;li&gt;AWS — Understanding and managing S3 storage classes: &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;li&gt;AWS — S3 Intelligent-Tiering (30d→IA 40%, 90d→Archive Instant 68%, no retrieval fee, 128 KB rule): &lt;a href="https://aws.amazon.com/s3/storage-classes/intelligent-tiering/" rel="noopener noreferrer"&gt;https://aws.amazon.com/s3/storage-classes/intelligent-tiering/&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;li&gt;AWS — How S3 Intelligent-Tiering works: &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/intelligent-tiering-overview.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/intelligent-tiering-overview.html&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;li&gt;AWS — Configuring lifecycle to delete incomplete multipart uploads (AbortIncompleteMultipartUpload, 7 days, no early-delete charge): &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpu-abort-incomplete-mpu-lifecycle-config.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpu-abort-incomplete-mpu-lifecycle-config.html&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;li&gt;AWS — S3 Storage Lens metrics &amp;amp; recommendations (62 free metrics, 14 days, incomplete MPU cost metric): &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage_lens_basics_metrics_recommendations.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage_lens_basics_metrics_recommendations.html&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;li&gt;AWS — Gateway endpoints for Amazon S3 (no charge, same-Region no data-transfer charge, caveats): &lt;a href="https://docs.aws.amazon.com/vpc/latest/privatelink/vpc-endpoints-s3.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/vpc/latest/privatelink/vpc-endpoints-s3.html&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;li&gt;AWS — S3 pricing page (storage / request / retrieval tables; live source of truth): &lt;a href="https://aws.amazon.com/s3/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/s3/pricing/&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;li&gt;AWS — VPC pricing (Interface endpoint $0.01/AZ/hr + $0.01/GB): &lt;a href="https://aws.amazon.com/vpc/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/vpc/pricing/&lt;/a&gt; — accessed 2026-06-18&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>costoptimization</category>
      <category>devops</category>
    </item>
    <item>
      <title>AWS Savings Plans vs Reserved Instances: Which to Buy</title>
      <dc:creator>Rajesh Medampudi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 15:51:44 +0000</pubDate>
      <link>https://dev.to/medampudi/aws-savings-plans-vs-reserved-instances-which-to-buy-pge</link>
      <guid>https://dev.to/medampudi/aws-savings-plans-vs-reserved-instances-which-to-buy-pge</guid>
      <description>&lt;p&gt;For AWS Savings Plans vs Reserved Instances, the default answer is: buy a Savings Plan, not a Reserved Instance. The exception is OpenSearch, Redshift, and (until December 2025) databases, which still need the older Reserved model. That is the whole decision. For most teams a Compute Savings Plan is the right default: same discount as a Convertible Reserved Instance, far less to manage, and it follows your workload across instance families, regions, Fargate, and Lambda. The cases where a Reserved Instance still wins are narrow and specific, and the December 2025 launch of Database Savings Plans shrank them further.&lt;/p&gt;

&lt;p&gt;This post is the &lt;em&gt;decision&lt;/em&gt; — RI vs Savings Plan, when each wins. It is not a deep-dive on how Savings Plans work under the hood; I cover the mechanics — how the $/hour commitment gets applied, the billing-hour math, the queue order against On-Demand — in a separate post. Here I only want to answer the question you actually have when the Cost Explorer recommendation pops up: &lt;em&gt;which one do I buy?&lt;/em&gt; (Commitments are step three of a full bill audit — where they sit in the order is in &lt;a href="https://dev.to/blog/audit-50k-aws-bill"&gt;what I'd audit first on a $50K AWS bill&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Both Reserved Instances and Savings Plans are the same trade: you promise AWS a one- or three-year commitment, AWS gives you a discount over On-Demand. The difference is &lt;strong&gt;what you commit to&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Reserved Instance&lt;/strong&gt; commits you to a specific instance configuration — family, and depending on type, size, region, OS, tenancy.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Savings Plan&lt;/strong&gt; commits you to a dollar amount of usage per hour (e.g. "$10/hour of compute"), and AWS applies that discount to whatever matching usage you actually run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Savings Plan is the more flexible instrument at the same discount level, which is why AWS itself now recommends Savings Plans over Reserved Instances for compute. (&lt;a href="https://docs.aws.amazon.com/savingsplans/latest/userguide/sp-ris.html" rel="noopener noreferrer"&gt;AWS, Compute Savings Plans and Reserved Instances&lt;/a&gt;, accessed June 2026.) The Reserved Instance has not gone away — but for EC2 compute, it is mostly the legacy choice now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The discount numbers, side by side
&lt;/h2&gt;

&lt;p&gt;The headline rates line up almost exactly. From AWS's own comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instrument&lt;/th&gt;
&lt;th&gt;Max discount vs On-Demand&lt;/th&gt;
&lt;th&gt;Flexibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compute Savings Plan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;up to 66%&lt;/td&gt;
&lt;td&gt;EC2 + Fargate + Lambda, any family, any region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EC2 Instance Savings Plan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;up to 72%&lt;/td&gt;
&lt;td&gt;one instance family in one region, any size/OS/tenancy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Convertible Reserved Instance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;up to 66%&lt;/td&gt;
&lt;td&gt;exchangeable, but manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Standard Reserved Instance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;up to 72%&lt;/td&gt;
&lt;td&gt;locked to configuration, best rate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(&lt;a href="https://docs.aws.amazon.com/savingsplans/latest/userguide/sp-ris.html" rel="noopener noreferrer"&gt;AWS, Compute Savings Plans and Reserved Instances&lt;/a&gt;, accessed June 2026.)&lt;/p&gt;

&lt;p&gt;That table is the entire argument. The Compute Savings Plan matches the Convertible RI's discount (up to 66%) but applies across instance families, regions, and serverless compute, automatically, with no exchanges. The EC2 Instance Savings Plan matches the Standard RI's discount (up to 72%) while still giving you size, OS, and tenancy flexibility within the family you committed to.&lt;/p&gt;

&lt;p&gt;So at every discount tier the Savings Plan gives you more flexibility for the same rate. That is why the default flipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standard vs Convertible RI — the old trade-off, briefly
&lt;/h2&gt;

&lt;p&gt;If you are still considering Reserved Instances, the choice between the two types is the same shape: more discount for less flexibility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard RIs&lt;/strong&gt; give the deepest discount (up to 72% off On-Demand) but you cannot change what you reserved — you can only modify size within a family (for Regional RIs) and sell unused ones on the Reserved Instance Marketplace. (&lt;a href="https://docs.aws.amazon.com/whitepapers/latest/cost-optimization-reservation-models/standard-vs.-convertible-offering-classes.html" rel="noopener noreferrer"&gt;AWS, Standard vs. Convertible offering classes&lt;/a&gt;, accessed June 2026.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convertible RIs&lt;/strong&gt; give a smaller discount (up to 66%) but you can exchange them for RIs with different attributes — different family, OS, tenancy — by performing a manual exchange. (&lt;a href="https://docs.aws.amazon.com/whitepapers/latest/cost-optimization-reservation-models/standard-vs.-convertible-offering-classes.html" rel="noopener noreferrer"&gt;AWS, Standard vs. Convertible offering classes&lt;/a&gt;, accessed June 2026.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the catch that makes Savings Plans the better tool for most teams. The Convertible RI's whole reason to exist is flexibility, and the Compute Savings Plan does that better with no manual exchanges. The Standard RI's reason to exist is the deepest rate, and the EC2 Instance Savings Plan matches it while staying more flexible. So for EC2, the RI is rarely the right answer in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a Reserved Instance still wins
&lt;/h2&gt;

&lt;p&gt;The Savings Plan does not cover everything. These are the real cases where you still reach for an RI or a Reserved Node:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Databases, before December 2025.&lt;/strong&gt; Until very recently, RDS, Aurora, ElastiCache, Redshift, and OpenSearch had no Savings Plan at all — Reserved Instances (or Reserved Nodes) were the &lt;em&gt;only&lt;/em&gt; way to get a committed-use discount on them. This is the single biggest reason teams still had large RI portfolios. The December 2025 launch changed most of this — see the next section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Redshift and OpenSearch — still Reserved-only.&lt;/strong&gt; Even after December 2025, &lt;strong&gt;Amazon Redshift uses Reserved Nodes&lt;/strong&gt; and &lt;strong&gt;Amazon OpenSearch Service uses Reserved Instances&lt;/strong&gt; as their committed-discount model. Neither is covered by any Savings Plan today. (&lt;a href="https://docs.aws.amazon.com/redshift/latest/mgmt/purchase-reserved-node-instance.html" rel="noopener noreferrer"&gt;AWS, Amazon Redshift reserved nodes&lt;/a&gt;; &lt;a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ri.html" rel="noopener noreferrer"&gt;AWS, Reserved Instances in Amazon OpenSearch Service&lt;/a&gt;, accessed June 2026.) If your bill is heavy on either, the Reserved model is not legacy — it is the only lever you have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Capacity guarantees.&lt;/strong&gt; A &lt;em&gt;zonal&lt;/em&gt; Reserved Instance reserves capacity in a specific Availability Zone. A Savings Plan does not reserve capacity at all — AWS is explicit that "Savings Plans doesn't provide capacity reservations." (&lt;a href="https://docs.aws.amazon.com/savingsplans/latest/userguide/sp-ris.html" rel="noopener noreferrer"&gt;AWS, Compute Savings Plans and Reserved Instances&lt;/a&gt;, accessed June 2026.) If you need a guarantee that the instance will be &lt;em&gt;available&lt;/em&gt; in a constrained AZ — not just discounted — you want a zonal RI or an On-Demand Capacity Reservation, not a Savings Plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The Reserved Instance Marketplace.&lt;/strong&gt; Standard RIs can be sold to other AWS customers if your needs change. Savings Plans cannot be cancelled or sold — "[they] can't be cancelled during the term." (&lt;a href="https://docs.aws.amazon.com/savingsplans/latest/userguide/sp-ris.html" rel="noopener noreferrer"&gt;AWS, Compute Savings Plans and Reserved Instances&lt;/a&gt;, accessed June 2026.) If you genuinely expect to need an exit and are willing to take a haircut, a Standard RI has a resale path a Savings Plan does not.&lt;/p&gt;

&lt;p&gt;Outside of these, the Savings Plan is the better instrument.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the December 2025 Database Savings Plans changed
&lt;/h2&gt;

&lt;p&gt;On &lt;strong&gt;2 December 2025&lt;/strong&gt;, AWS launched &lt;strong&gt;Database Savings Plans&lt;/strong&gt;, finally bringing the Savings Plan model to managed databases — the gap that kept most database spend stuck on Reserved Instances. (&lt;a href="https://aws.amazon.com/blogs/aws/introducing-database-savings-plans-for-aws-databases/" rel="noopener noreferrer"&gt;AWS, Introducing Database Savings Plans for AWS Databases&lt;/a&gt;, accessed June 2026.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it covers.&lt;/strong&gt; The supported services are: &lt;strong&gt;Amazon Aurora, Amazon RDS, Amazon DynamoDB, Amazon ElastiCache, Amazon DocumentDB, Amazon Neptune, Amazon Keyspaces, Amazon Timestream, and AWS Database Migration Service (DMS).&lt;/strong&gt; (&lt;a href="https://aws.amazon.com/blogs/aws/introducing-database-savings-plans-for-aws-databases/" rel="noopener noreferrer"&gt;AWS, Introducing Database Savings Plans&lt;/a&gt;, accessed June 2026.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does NOT cover.&lt;/strong&gt; Note two absences that matter: &lt;strong&gt;Amazon Redshift and Amazon OpenSearch Service are not on the list.&lt;/strong&gt; They remain Reserved-Node / Reserved-Instance only. So if you read a summary that says Database Savings Plans cover OpenSearch, it is wrong — check the official service list. Redshift and OpenSearch are exactly the two database-family services where the RI/Reserved-Node model is still the &lt;em&gt;only&lt;/em&gt; committed-discount option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The discount.&lt;/strong&gt; Database Savings Plans save up to &lt;strong&gt;35% on serverless&lt;/strong&gt; deployments and up to &lt;strong&gt;20% on provisioned instances&lt;/strong&gt;, with smaller tiers for DynamoDB and Keyspaces throughput. (&lt;a href="https://aws.amazon.com/blogs/aws/introducing-database-savings-plans-for-aws-databases/" rel="noopener noreferrer"&gt;AWS, Introducing Database Savings Plans&lt;/a&gt;, accessed June 2026.) These are lower than EC2's headline numbers — databases were always a shallower discount — but the flexibility is the same draw: the plan applies regardless of engine, instance family, size, deployment option, or region, so you can move an Aurora workload from &lt;code&gt;db.r7g&lt;/code&gt; to &lt;code&gt;db.r8g&lt;/code&gt;, shift regions, or modernise from RDS for Oracle to Aurora PostgreSQL and keep the discount. (&lt;a href="https://aws.amazon.com/blogs/aws/introducing-database-savings-plans-for-aws-databases/" rel="noopener noreferrer"&gt;AWS, Introducing Database Savings Plans&lt;/a&gt;, accessed June 2026.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The terms — read this before you commit.&lt;/strong&gt; As launched, Database Savings Plans are &lt;strong&gt;one-year term, No Upfront payment only.&lt;/strong&gt; (&lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/12/database-savings-plans-savings/" rel="noopener noreferrer"&gt;AWS, Announcing Database Savings Plans&lt;/a&gt;, accessed June 2026.) There is no three-year option and no upfront-payment option at launch — which is a meaningfully different shape from EC2 Savings Plans and from database Reserved Instances, both of which offer 1- or 3-year terms and All/Partial/No Upfront. If you have been buying 3-year database RIs for the deepest rate, the Savings Plan does not yet replace that specific play. Available in all AWS Regions except China. (&lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/12/database-savings-plans-savings/" rel="noopener noreferrer"&gt;AWS, Announcing Database Savings Plans&lt;/a&gt;, accessed June 2026.)&lt;/p&gt;

&lt;p&gt;The practical effect: for &lt;strong&gt;RDS, Aurora, ElastiCache, DocumentDB, Neptune, DynamoDB, Keyspaces, Timestream, and DMS&lt;/strong&gt;, you now have a flexible Savings Plan option that did not exist before December 2025, and for steady-state database spend on a 1-year horizon it is usually the easier instrument to manage than database RIs. For &lt;strong&gt;Redshift and OpenSearch&lt;/strong&gt;, nothing changed — keep buying Reserved Nodes / Reserved Instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision, step by step
&lt;/h2&gt;

&lt;p&gt;Here is how I would route the decision today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is it Redshift or OpenSearch?&lt;/strong&gt; → Reserved Node (Redshift) or Reserved Instance (OpenSearch). No Savings Plan exists. Done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it another managed database (RDS, Aurora, ElastiCache, DynamoDB, DocumentDB, Neptune, Keyspaces, Timestream, DMS)?&lt;/strong&gt; → Database Savings Plan (1-year, No Upfront) for steady-state spend. Database RIs only if you specifically need a 3-year term or upfront payment for a deeper rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it EC2 / Fargate / Lambda compute?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Do you need flexibility across instance families and regions, or you run Fargate/Lambda? → &lt;strong&gt;Compute Savings Plan&lt;/strong&gt; (up to 66%).&lt;/li&gt;
&lt;li&gt;Is your usage concentrated and stable in one instance family in one region, and you want the deepest rate? → &lt;strong&gt;EC2 Instance Savings Plan&lt;/strong&gt; (up to 72%).&lt;/li&gt;
&lt;li&gt;Do you need a &lt;em&gt;capacity guarantee&lt;/em&gt; in a specific AZ, or a resale exit? → &lt;strong&gt;Reserved Instance&lt;/strong&gt; (zonal for capacity; Standard for the Marketplace).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else (steady compute, no special constraint)?&lt;/strong&gt; → &lt;strong&gt;Compute Savings Plan.&lt;/strong&gt; It is the safe default.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The honest summary: Savings Plans are the default for almost everything now, Database Savings Plans closed the biggest remaining gap in December 2025, and Reserved Instances survive in three specific corners — Redshift/OpenSearch, capacity guarantees, and the resale exit.&lt;/p&gt;

&lt;p&gt;One caveat I will state plainly: I have not personally run a Database Savings Plan commitment for a full year yet — it launched in December 2025 and I am writing this in 2026. The EC2 Savings Plan guidance is from production use; the database guidance is from the AWS documentation and the launch terms, which I have verified and cited above. Treat the database section as "what the docs say and what I would do," not "what I have run for twelve months."&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS — Compute Savings Plans and Reserved Instances (comparison table, discounts, flexibility, "no capacity reservations", "can't be cancelled"): &lt;a href="https://docs.aws.amazon.com/savingsplans/latest/userguide/sp-ris.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/savingsplans/latest/userguide/sp-ris.html&lt;/a&gt; — accessed June 2026.&lt;/li&gt;
&lt;li&gt;AWS — Standard vs. Convertible offering classes (Standard up to 72% / Convertible up to 66%, exchange vs no-exchange): &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/cost-optimization-reservation-models/standard-vs.-convertible-offering-classes.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/whitepapers/latest/cost-optimization-reservation-models/standard-vs.-convertible-offering-classes.html&lt;/a&gt; — accessed June 2026.&lt;/li&gt;
&lt;li&gt;AWS — Introducing Database Savings Plans for AWS Databases (launch 2 Dec 2025, supported services list, up to 35% serverless / 20% provisioned, engine/family/region flexibility): &lt;a href="https://aws.amazon.com/blogs/aws/introducing-database-savings-plans-for-aws-databases/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/aws/introducing-database-savings-plans-for-aws-databases/&lt;/a&gt; — accessed June 2026.&lt;/li&gt;
&lt;li&gt;AWS — Announcing Database Savings Plans with up to 35% savings (1-year term, No Upfront only, all Regions except China): &lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/12/database-savings-plans-savings/" rel="noopener noreferrer"&gt;https://aws.amazon.com/about-aws/whats-new/2025/12/database-savings-plans-savings/&lt;/a&gt; — accessed June 2026.&lt;/li&gt;
&lt;li&gt;AWS — Amazon Redshift reserved nodes (Redshift remains Reserved-Node only): &lt;a href="https://docs.aws.amazon.com/redshift/latest/mgmt/purchase-reserved-node-instance.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/redshift/latest/mgmt/purchase-reserved-node-instance.html&lt;/a&gt; — accessed June 2026.&lt;/li&gt;
&lt;li&gt;AWS — Reserved Instances in Amazon OpenSearch Service (OpenSearch remains Reserved-Instance only; 1/3-year, No/Partial/All Upfront): &lt;a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ri.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ri.html&lt;/a&gt; — accessed June 2026.&lt;/li&gt;
&lt;li&gt;AWS — Compute and EC2 Instance Savings Plans pricing (1 or 3 year terms, up to 66% / 72%): &lt;a href="https://aws.amazon.com/savingsplans/compute-pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/savingsplans/compute-pricing/&lt;/a&gt; — accessed June 2026.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>costoptimization</category>
      <category>finops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>AWS NAT Gateway Pricing: The Hidden Tax, and How to Kill It</title>
      <dc:creator>Rajesh Medampudi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 15:51:41 +0000</pubDate>
      <link>https://dev.to/medampudi/aws-nat-gateway-pricing-the-hidden-tax-and-how-to-kill-it-527d</link>
      <guid>https://dev.to/medampudi/aws-nat-gateway-pricing-the-hidden-tax-and-how-to-kill-it-527d</guid>
      <description>&lt;p&gt;If your AWS bill has a NAT Gateway line, you are paying twice for the same packet: once for the gateway to merely exist, and again for every gigabyte it carries. The fix for most teams is dull and free. Add an S3 and a DynamoDB gateway endpoint, route the heavy traffic away from NAT, and only then argue about anything fancier. That single change is free to turn on, takes minutes, and stops the most expensive traffic from ever touching the meter.&lt;/p&gt;

&lt;p&gt;This is a playbook, not a lecture. The trick with NAT Gateway pricing is that the two charges hide in different places on the bill, so most teams only ever see half of it. Numbers first, then the fixes, in the order I would actually do them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you are actually being charged for
&lt;/h2&gt;

&lt;p&gt;NAT Gateway has two charges, and people forget the second one until they read the bill closely.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hourly charge&lt;/strong&gt; — you pay for every hour the gateway is provisioned and available, whether or not a single byte moves through it. In us-east-1 (N. Virginia) and us-east-2 (Ohio) this is &lt;strong&gt;$0.045 per NAT Gateway-hour&lt;/strong&gt;. That is roughly &lt;strong&gt;$32.85 a month&lt;/strong&gt; per gateway just to keep the lights on. Partial hours bill as full hours.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data processing charge&lt;/strong&gt; — you pay &lt;strong&gt;$0.045 per GB&lt;/strong&gt; processed through the gateway, in the same region, on top of the hourly charge. This applies to every gigabyte, inbound or outbound, regardless of source or destination.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And then there is the part the pricing page mentions almost in passing: &lt;strong&gt;standard AWS data transfer charges still apply on top.&lt;/strong&gt; NAT processing is an extra meter on traffic you were already paying to move.&lt;/p&gt;

&lt;p&gt;The hourly charge is fixed and visible. The per-GB charge is the one that catches teams out, because it scales with traffic you mostly cannot see: package installs, container image pulls, S3 reads from private subnets, telemetry shipped out, cross-region calls. The rate varies by region (it runs higher in places like São Paulo, where both the hourly and per-GB rates sit around $0.093), so check your own region rather than trusting the Ohio number. (AWS VPC Pricing; NAT Gateway pricing docs, accessed June 2026.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it hides
&lt;/h2&gt;

&lt;p&gt;Three reasons the NAT line stays invisible until it is large.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is bundled with traffic you think is "free."&lt;/strong&gt; A private subnet pulling a Docker image from a public registry, or an app reading from S3 over the public S3 endpoint, looks like ordinary egress. Every one of those gigabytes is also a NAT data-processing gigabyte at $0.045.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It scales with success, not with headcount.&lt;/strong&gt; You provision one NAT Gateway on day one and forget it. Traffic grows with users and deploys; the per-GB charge grows with it, silently, while the line item stays named the same boring thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-AZ traffic gets routed through it the expensive way.&lt;/strong&gt; If your workloads sit in one Availability Zone and your NAT Gateway sits in another, that traffic crosses an AZ boundary to reach NAT and crosses back — racking up inter-AZ data transfer charges &lt;em&gt;in addition to&lt;/em&gt; NAT processing. AWS's own guidance is explicit: keep resources in the same AZ as the NAT Gateway, or run a NAT Gateway in each AZ that has resources. (NAT Gateway pricing docs, accessed June 2026.)&lt;/p&gt;

&lt;h2&gt;
  
  
  How to spot it on your bill
&lt;/h2&gt;

&lt;p&gt;Before you fix anything, measure. Fifteen minutes in the console tells you whether this is a $30 problem or a $3,000 one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost Explorer&lt;/strong&gt; — group by &lt;em&gt;Usage Type&lt;/em&gt; and filter for &lt;code&gt;NatGateway-Hours&lt;/code&gt; and &lt;code&gt;NatGateway-Bytes&lt;/code&gt;. The first is your fixed cost (number of gateways × hours). The second is the one worth chasing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC Flow Logs&lt;/strong&gt; — turn them on for the NAT'd subnets and look at where the bytes go. If a large share of destinations are S3 or DynamoDB IP ranges, that is your easiest win: those bytes should never touch NAT.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The smell test&lt;/strong&gt; — a single NAT Gateway carrying multiple TB a month, in an account doing heavy S3 or container work, almost always means you are paying NAT to do a job a free endpoint should be doing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fixes, in the order I would do them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Add S3 and DynamoDB gateway endpoints — free, do this today
&lt;/h3&gt;

&lt;p&gt;This is the first move and it is not close. &lt;strong&gt;Gateway VPC endpoints for Amazon S3 and DynamoDB carry no hourly charge and no data-processing charge.&lt;/strong&gt; AWS states it plainly: there are no data-processing or hourly charges for using Gateway-type VPC endpoints. (AWS VPC Pricing; Gateway endpoints docs, accessed June 2026.)&lt;/p&gt;

&lt;p&gt;A gateway endpoint adds a route to your subnet's route table so traffic to S3 or DynamoDB goes over a private AWS path instead of out through NAT. Every gigabyte you move to that path is a gigabyte that stops costing you $0.045 of NAT processing. If you run anything S3-heavy from private subnets — backups, data pipelines, log shipping, static asset reads — this one change can take a meaningful bite out of the bill for the price of a route-table edit.&lt;/p&gt;

&lt;p&gt;There is no real downside. Create one in any VPC that talks to S3 or DynamoDB. The only catch worth knowing: gateway endpoints work from inside the VPC only — they do not serve on-premises networks, peered VPCs in other regions, or transit gateways. For those, you need the interface type below.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add interface (PrivateLink) endpoints for the next-heaviest services
&lt;/h3&gt;

&lt;p&gt;Gateway endpoints exist for exactly two services. Everything else — ECR (container image pulls), CloudWatch Logs, SSM, Secrets Manager, STS, Kinesis, and so on — uses &lt;strong&gt;interface endpoints&lt;/strong&gt;, which run on AWS PrivateLink and are &lt;em&gt;not&lt;/em&gt; free.&lt;/p&gt;

&lt;p&gt;Interface endpoint pricing: &lt;strong&gt;$0.01 per hour per endpoint, per Availability Zone&lt;/strong&gt;, plus &lt;strong&gt;$0.01 per GB&lt;/strong&gt; processed (tiered down for very large volumes — $0.006/GB past 1 PB, $0.004/GB past 5 PB). (AWS PrivateLink pricing, accessed June 2026.)&lt;/p&gt;

&lt;p&gt;So an interface endpoint is roughly &lt;strong&gt;$7.30 a month per AZ&lt;/strong&gt; in hourly cost, plus a per-GB rate that is &lt;strong&gt;less than a quarter of NAT's $0.045/GB&lt;/strong&gt;. The decision is arithmetic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-volume service traffic (think ECR image pulls on every deploy, or chatty CloudWatch Logs) — the $0.01/GB endpoint beats the $0.045/GB NAT path easily, and you stop paying twice.&lt;/li&gt;
&lt;li&gt;Low-volume, occasional traffic — the per-AZ hourly fee can cost &lt;em&gt;more&lt;/em&gt; than just letting it ride over NAT. Do the math per service. A handful of MB a month does not justify $7.30 × number-of-AZs in standing charges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The classic win here is &lt;strong&gt;ECR&lt;/strong&gt;. Container-heavy accounts pull large images through NAT on every deploy and autoscale event; moving ECR (plus its S3 backing layer via the gateway endpoint, and CloudWatch Logs) onto endpoints is often where the NAT bill collapses.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Fix the cross-AZ routing
&lt;/h3&gt;

&lt;p&gt;If your NAT Gateway lives in one AZ and your workloads in another, you are paying inter-AZ transfer on top of NAT processing. Either co-locate the workloads with the gateway, or run one NAT Gateway per AZ that has resources so traffic never crosses a boundary to reach it. Per-AZ NAT Gateways cost more in fixed hourly fees but can be cheaper overall once cross-AZ transfer is large — again, measure before deciding. (NAT Gateway pricing docs, accessed June 2026.)&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Question whether you need NAT at all
&lt;/h3&gt;

&lt;p&gt;A surprising number of private subnets only ever talk to AWS services and a short list of known external hosts. Once S3, DynamoDB, ECR, and your logging/secrets services are on endpoints, what is actually left going to the public internet? Sometimes the honest answer is "almost nothing," and the NAT Gateway is $32.85/month of standing charge guarding a trickle. If a workload needs no inbound and only talks to AWS, it may not need NAT or even a public path at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Replace NAT with a NAT instance (fck-nat) — only with eyes open
&lt;/h3&gt;

&lt;p&gt;For the cost-sensitive, the open-source pattern is &lt;strong&gt;fck-nat&lt;/strong&gt;: a self-managed NAT &lt;em&gt;instance&lt;/em&gt; on a small ARM box instead of the managed gateway. On a &lt;code&gt;t4g.nano&lt;/code&gt; it runs at roughly &lt;strong&gt;$3–4 a month&lt;/strong&gt;, supports up to ~5 Gbps of burst traffic (the EC2 egress cap), and — the real saving — &lt;strong&gt;charges no per-GB processing fee&lt;/strong&gt;, because it is just an EC2 instance forwarding packets. Reported savings run up to ~90%+ versus managed NAT. (fck-nat project, accessed June 2026.)&lt;/p&gt;

&lt;p&gt;But be honest about what you are buying. The managed NAT Gateway is fully managed and highly available within its AZ; fck-nat is an instance &lt;em&gt;you&lt;/em&gt; own. By default it has &lt;strong&gt;no automatic failover&lt;/strong&gt; — if the instance dies, that subnet's egress dies until it recovers. You handle patching, monitoring, and scaling. The project's own authors recommend the managed gateway for workloads that need five-nines uptime. (fck-nat project, accessed June 2026.)&lt;/p&gt;

&lt;p&gt;So the rule I would apply: &lt;strong&gt;fck-nat for dev, staging, and internal/batch workloads where a few minutes of egress downtime is survivable. Managed NAT Gateway for production paths where it is not.&lt;/strong&gt; This is the one fix on the list with a real operational cost; do not reach for it before you have done the free endpoints, which carry no trade-off at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order is the point
&lt;/h2&gt;

&lt;p&gt;The reason to do these in sequence is that the cheap, no-downside fixes capture most of the savings. Gateway endpoints are free and risk-free; do them first. Interface endpoints are cheap and a simple cost calculation; do them next, per service. Cross-AZ and architecture cleanups are free but need a little thought. fck-nat is the only one that trades money for operational responsibility, so it goes last and only where the trade is acceptable.&lt;/p&gt;

&lt;p&gt;Most teams never need to get past step two. The S3 and DynamoDB endpoints alone are free, take minutes, and remove the single most common reason the NAT line creeps up. If you read only one sentence of this and act on it, make it that one.&lt;/p&gt;

&lt;p&gt;NAT is usually the first place I look on a mid-size bill — the broader order I work an account in is &lt;a href="https://dev.to/blog/audit-50k-aws-bill"&gt;what I'd audit first on a $50K AWS bill&lt;/a&gt;, and the S3 side of the same traffic problem is the &lt;a href="https://dev.to/blog/s3-cost-optimization-playbook"&gt;S3 cost optimization playbook&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Amazon VPC Pricing — NAT Gateway $0.045/hr + $0.045/GB (us-east, Ohio); gateway endpoints for S3/DynamoDB have no hourly or data-processing charges. &lt;a href="https://aws.amazon.com/vpc/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/vpc/pricing/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pricing for NAT gateways (VPC User Guide) — two-charge model; same-AZ recommendation to avoid cross-AZ transfer; guidance to use interface/gateway endpoints for AWS-service traffic. &lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-pricing.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-pricing.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS PrivateLink Pricing — interface endpoint $0.01/hr per AZ + $0.01/GB (tiered to $0.006/GB past 1 PB, $0.004/GB past 5 PB). &lt;a href="https://aws.amazon.com/privatelink/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/privatelink/pricing/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gateway endpoints (PrivateLink docs) — S3/DynamoDB gateway endpoints, no PrivateLink, no additional charge; VPC-internal only (no on-prem / cross-region peering / transit gateway). &lt;a href="https://docs.aws.amazon.com/vpc/latest/privatelink/gateway-endpoints.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/vpc/latest/privatelink/gateway-endpoints.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;fck-nat (open-source NAT instance AMI) — t4g.nano ~$3-4/mo, ~5 Gbps burst, no per-GB processing fee, no default auto-failover; authors recommend managed NAT for five-nines. &lt;a href="https://github.com/AndrewGuenther/fck-nat" rel="noopener noreferrer"&gt;https://github.com/AndrewGuenther/fck-nat&lt;/a&gt; and &lt;a href="https://fck-nat.dev" rel="noopener noreferrer"&gt;https://fck-nat.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>networking</category>
      <category>costoptimization</category>
    </item>
    <item>
      <title>AWS Cost Optimization: What I'd Audit First on a $50K Bill</title>
      <dc:creator>Rajesh Medampudi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 15:51:38 +0000</pubDate>
      <link>https://dev.to/medampudi/aws-cost-optimization-what-id-audit-first-on-a-50k-bill-4bk1</link>
      <guid>https://dev.to/medampudi/aws-cost-optimization-what-id-audit-first-on-a-50k-bill-4bk1</guid>
      <description>&lt;p&gt;Give me read access to a $50,000/month AWS account and I will tell you within a day where the first 20-30% is hiding, because on a mid-size bill it is almost always hiding in the same four places, in the same order: data transfer you can't see in the console, instances sized for a load test that ran two years ago, on-demand pricing on a baseline that never moves, and storage rotting in the most expensive class AWS sells. None of this needs an architecture rewrite. AWS cost optimization, at least the first and biggest pass of it, is just the bill read in the right order by someone who knows where AWS buries the meter.&lt;/p&gt;

&lt;p&gt;This is the order I work. It is the same audit I run on every account I'm handed, and it is the offer — if you want me to run it on yours, the post ends with how. But you can run most of it yourself today, and you should, because nobody is going to care about your bill as much as you do.&lt;/p&gt;

&lt;p&gt;A note before the recipe: I deal in ranges, not promises. The exact saving on your account depends on what you've built. What I can promise is that the mistakes below are common enough that the question is usually &lt;em&gt;how much&lt;/em&gt;, not &lt;em&gt;whether&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hour zero: get the real bill, not the dashboard
&lt;/h2&gt;

&lt;p&gt;Before touching a single resource, I want the granular data. The AWS console's cost dashboard rounds, groups, and hides the things that matter. Two tools give you the truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost Explorer&lt;/strong&gt;, with rightsizing recommendations turned on, is the fast view — group by service, then by usage type, and the bill stops being one big number and starts being a list of decisions. Resource-level and hourly granularity costs extra ($0.01 per 1,000 usage records per month), but for one audit pass it's worth pennies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Cost and Usage Report (CUR)&lt;/strong&gt; is the ground truth — line-item, hourly, every charge AWS makes, delivered to your own S3 bucket. Generating it is free; you pay only the few cents of S3 storage. If you're going to do this seriously, set up CUR (now delivered via AWS Data Exports) on day one. Everything below is a query against it.&lt;/p&gt;

&lt;p&gt;The first thing I look at isn't a resource. It's the shape of the bill: what fraction is compute, what fraction is storage, what fraction is the line most people never read — &lt;strong&gt;data transfer&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  First place I look: data transfer (the invisible 15%)
&lt;/h2&gt;

&lt;p&gt;This is first because it's the one nobody instruments, and on a networked workload it's frequently the single most wasteful line. AWS charges for moving bytes, and the meter runs in places the console never surfaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NAT Gateway.&lt;/strong&gt; This is the one I find money in most often. A NAT Gateway costs &lt;strong&gt;$0.045 per hour&lt;/strong&gt; just to exist, &lt;em&gt;plus&lt;/em&gt; &lt;strong&gt;$0.045 for every GB it processes&lt;/strong&gt; (us-east-1). The hourly charge is trivial; the per-GB charge is where it hurts. If your private-subnet instances pull container images, packages, or — the classic — objects from S3 through the NAT Gateway, you are paying 4.5 cents a GB to route traffic that should be free. A &lt;strong&gt;VPC Gateway Endpoint for S3 and DynamoDB costs nothing&lt;/strong&gt; and takes that traffic off the NAT path entirely. I check this on every account, and on data-heavy ones it's often the biggest single line-item fix. (The full NAT teardown — both charges, every fix — is its own post: &lt;a href="https://dev.to/blog/aws-nat-gateway-hidden-tax"&gt;the NAT Gateway hidden tax&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-AZ traffic.&lt;/strong&gt; AWS charges &lt;strong&gt;$0.01/GB in each direction&lt;/strong&gt; for data crossing Availability Zones in the same region — $0.02 round trip. Spread a chatty app and its database across three AZs for "high availability" and you can pay a real tax on every query. Sometimes the HA is worth it. Often the chattiness is an accident of where things got scheduled, and pinning the hot path to one AZ (while keeping failover) cuts the line without cutting resilience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data transfer out to the internet.&lt;/strong&gt; First 100 GB/month is free, aggregated across all services and regions; after that it's &lt;strong&gt;$0.09/GB&lt;/strong&gt; up to 10 TB. If you're serving meaningful traffic straight off EC2 or an ALB, CloudFront in front often costs less per GB &lt;em&gt;and&lt;/em&gt; offloads the origin — the egress math frequently pays for the CDN by itself.&lt;/p&gt;

&lt;p&gt;The reason this is step one: data transfer is the only major cost that doesn't show up as a resource you can point at. You have to read it out of the bill on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second: rightsizing — the load test that never ended
&lt;/h2&gt;

&lt;p&gt;Now the obvious one, done properly. Most over-provisioning isn't malice; it's an &lt;code&gt;m5.2xlarge&lt;/code&gt; somebody picked for a launch-day spike that never came back, running at 8% CPU ever since.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Compute Optimizer&lt;/strong&gt; is free and does the heavy lifting. It analyses CloudWatch metrics and gives rightsizing recommendations for EC2, Auto Scaling groups, EBS volumes, Lambda, and ECS-on-Fargate (and, as of 2026, RDS and idle-resource recommendations for things like NAT Gateways too). Default lookback is 14 days; pay a small per-resource fee for Enhanced Infrastructure Metrics and it'll look back ~93 days, which I'd do before resizing anything seasonal — you don't want to shrink a box right before its busy month.&lt;/p&gt;

&lt;p&gt;My discipline here: I trust the &lt;em&gt;direction&lt;/em&gt; of the recommendation, not the exact target. Compute Optimizer is right that the box is too big; whether you drop one size or two depends on headroom you understand and it doesn't. Rightsizing is also the one step you can get &lt;em&gt;wrong&lt;/em&gt; — under-provision a latency-sensitive service and you've traded a cost problem for an outage. Move one size at a time, watch the metrics, repeat.&lt;/p&gt;

&lt;p&gt;Two rightsizing moves that aren't just "smaller":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Move to gp3 EBS.&lt;/strong&gt; gp2 is $0.10/GB-month; &lt;strong&gt;gp3 is $0.08/GB-month — 20% cheaper, by AWS's own number&lt;/strong&gt; — and gp3 includes 3,000 IOPS and 125 MB/s baseline free. For the overwhelming majority of volumes, gp3 is the correct default and gp2 is just an older, costlier setting nobody changed. io2 is for genuine high-IOPS workloads only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move to Graviton.&lt;/strong&gt; AWS's ARM64 chips run at materially lower cost than comparable x86 — the current Graviton page cites &lt;em&gt;up to 20% less cost&lt;/em&gt; (the older "up to 40% better price-performance" figure was the Graviton2-generation claim). If your stack is interpreted or already cross-compiles cleanly (most Go, Java, Python, Node services do), the migration is often a base-image change and a redeploy. On the observability platform I ran on EKS — Grafana LGTM, 6 TB/day for 15 departments and 200+ developers — moving the fleet to ARM Graviton2 under Karpenter took 40% off the compute line, with no performance loss. Java compiled cleanly to ARM64; the work was node-pool config and a redeploy, not a rewrite.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Third: commitments — stop paying on-demand for a baseline that never moves
&lt;/h2&gt;

&lt;p&gt;Rightsize &lt;em&gt;first&lt;/em&gt;, commit &lt;em&gt;second&lt;/em&gt; — never the other way round, or you'll buy a commitment for capacity you're about to delete.&lt;/p&gt;

&lt;p&gt;Once the fleet is the right size, look at the baseline that runs 24/7. Paying on-demand for steady-state compute is leaving the largest predictable discount on the table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Savings Plans&lt;/strong&gt; are the current answer for compute. A &lt;strong&gt;Compute Savings Plan&lt;/strong&gt; gives up to &lt;strong&gt;66%&lt;/strong&gt; off and — this is the point — applies across EC2, Fargate, and Lambda, across regions, instance families, and operating systems. You commit to a dollar-per-hour spend, not a specific instance, so it keeps applying as your fleet changes. An &lt;strong&gt;EC2 Instance Savings Plan&lt;/strong&gt; goes deeper, up to &lt;strong&gt;72%&lt;/strong&gt;, in exchange for locking to an instance family in a region. &lt;strong&gt;Standard Reserved Instances&lt;/strong&gt; also reach up to 72% but are rigid; for EC2, Savings Plans have largely superseded them.&lt;/p&gt;

&lt;p&gt;The trap I see: people assume Savings Plans cover everything. &lt;strong&gt;Compute Savings Plans do not cover databases.&lt;/strong&gt; For that, AWS launched a separate &lt;strong&gt;Database Savings Plans&lt;/strong&gt; product (December 2025) covering RDS, Aurora, ElastiCache (Valkey), OpenSearch, DynamoDB and more — up to ~35%. &lt;strong&gt;Redshift&lt;/strong&gt; remains Reserved-Node-only. So on a real bill you may need two or three commitment instruments, not one. Map them to the &lt;em&gt;steady-state&lt;/em&gt; portion of each — not the peak, or you'll over-commit and pay for unused commitment.&lt;/p&gt;

&lt;p&gt;Rule of thumb I hold to: commit to the floor, pay on-demand (or Spot) for the spikes. Start with a 1-year, no-upfront Compute Savings Plan sized to your reliable baseline. You can always commit deeper once you trust the number. (Which instrument to buy — and why Savings Plans beat Reserved Instances for most teams — is the &lt;a href="https://dev.to/blog/ri-vs-savings-plans"&gt;Reserved Instances vs Savings Plans&lt;/a&gt; decision guide.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Fourth: storage and the things nobody deleted
&lt;/h2&gt;

&lt;p&gt;Storage is rarely the biggest line, but it's the easiest free money, because most of it is waste nobody is defending.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3 in the wrong class.&lt;/strong&gt; S3 Standard is &lt;strong&gt;$0.023/GB-month&lt;/strong&gt; — the most expensive tier — and most data sitting in it hasn't been read in months. For unpredictable access, &lt;strong&gt;S3 Intelligent-Tiering&lt;/strong&gt; moves objects between tiers automatically with no retrieval fees and no operational overhead (you pay $0.0025 per 1,000 objects/month for monitoring). For known-cold data, a lifecycle policy to Standard-IA ($0.0125), Glacier Instant Retrieval ($0.004), or Glacier Deep Archive (~$0.001) cuts the line by 5-20x. Standard at $0.023 vs Deep Archive at ~$0.001 is not a rounding difference; it's a 20x difference on the same bytes. (The whole S3 lever — classes, lifecycle, multipart cleanup, retrieval fees — is the &lt;a href="https://dev.to/blog/s3-cost-optimization-playbook"&gt;S3 cost optimization playbook&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The graveyard.&lt;/strong&gt; Three things bill silently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unattached EBS volumes&lt;/strong&gt; keep billing per GB-month after the instance they served is long gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old EBS snapshots&lt;/strong&gt; — $0.05/GB-month each — accumulate forever unless something deletes them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public IPv4 addresses.&lt;/strong&gt; Since &lt;strong&gt;1 February 2024, AWS charges $0.005/hour for every public IPv4 address, attached or not&lt;/strong&gt; — about $3.60/month each. On an account with a sprawl of idle Elastic IPs and load balancers, this adds up to a line worth reading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AWS Trusted Advisor&lt;/strong&gt; flags most of this — idle load balancers, underutilised EBS volumes, unassociated Elastic IPs — but the full cost-optimization check set requires a paid Support plan (Business Support and above). If you're on Basic, Compute Optimizer plus a CUR query gets you most of the same answers for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order is the recipe
&lt;/h2&gt;

&lt;p&gt;The sequence matters more than any single fix:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get the real bill&lt;/strong&gt; — CUR + Cost Explorer, read the shape before touching anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data transfer first&lt;/strong&gt; — it's invisible and often the biggest single waste (NAT Gateway, cross-AZ, egress).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rightsize&lt;/strong&gt; — Compute Optimizer for direction, gp3 and Graviton for structural wins, one size at a time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit&lt;/strong&gt; — Savings Plans on the rightsized baseline, never before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage cleanup&lt;/strong&gt; — S3 classes, then delete the graveyard.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run it top to bottom and the early steps make the later ones cheaper — you don't want to buy a 3-year commitment on an instance you're about to delete, or rightsize a fleet before you've stopped it routing free traffic through a paid NAT Gateway.&lt;/p&gt;

&lt;p&gt;I've run this enough times that the pattern is boringly consistent. On the cost work I take on, the range I quote is 20-40% with no performance loss — and on a mid-size bill the first pass usually lands in that band without touching the architecture. The exact number is yours to discover; the order is the same on every account.&lt;/p&gt;




&lt;p&gt;If you'd rather not run it yourself: this audit &lt;em&gt;is&lt;/em&gt; my AWS cost-optimization offer. I read your bill, run this sequence against your CUR, and hand you a prioritised list — biggest, safest wins first, with the numbers attached. No architecture rewrite, no lock-in, no commitment to me beyond the audit. Reach me through &lt;a href="https://rajesh.medampudi.com/work-with-me" rel="noopener noreferrer"&gt;rajesh.medampudi.com/work-with-me&lt;/a&gt; — a short email about what you're dealing with is the best starting point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;(all checked 2026-06-18, us-east-1 unless noted)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/vpc/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/vpc/pricing/&lt;/a&gt; — NAT Gateway $0.045/hr + $0.045/GB; VPC Gateway Endpoint for S3/DynamoDB is free&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-pricing.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-pricing.html&lt;/a&gt; — NAT Gateway pricing detail&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/ec2/pricing/on-demand/" rel="noopener noreferrer"&gt;https://aws.amazon.com/ec2/pricing/on-demand/&lt;/a&gt; — DTO first 100GB/mo free then $0.09/GB to 10TB; cross-AZ $0.01/GB each direction&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/ebs/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/ebs/pricing/&lt;/a&gt; — gp2 $0.10, gp3 $0.08 (GB-mo); snapshots $0.05/GB-mo; unattached volumes still bill&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/ebs/general-purpose/" rel="noopener noreferrer"&gt;https://aws.amazon.com/ebs/general-purpose/&lt;/a&gt; — gp3 "20% less expensive than gp2", 3000 IOPS + 125 MB/s baseline included&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html&lt;/a&gt; — gp3 as default, io2 for high-IOPS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/s3/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/s3/pricing/&lt;/a&gt; — S3 Standard $0.023, Standard-IA $0.0125, Glacier Instant $0.004, Flexible $0.0036, Deep Archive ~$0.00099 (GB-mo); Intelligent-Tiering monitoring $0.0025/1000 objects&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/s3/storage-classes/intelligent-tiering/" rel="noopener noreferrer"&gt;https://aws.amazon.com/s3/storage-classes/intelligent-tiering/&lt;/a&gt; — Intelligent-Tiering auto-tiers, no retrieval fees; lifecycle transition request costs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/savingsplans/compute-pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/savingsplans/compute-pricing/&lt;/a&gt; — Compute SP up to 66%, EC2 Instance SP up to 72%; covers EC2/Fargate/Lambda across regions/families&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/savingsplans/database-pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/savingsplans/database-pricing/&lt;/a&gt; — Database Savings Plans (Dec 2025) cover RDS/Aurora/ElastiCache-Valkey/OpenSearch/DynamoDB up to ~35%&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/blogs/aws/introducing-database-savings-plans-for-aws-databases/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/aws/introducing-database-savings-plans-for-aws-databases/&lt;/a&gt; — Database Savings Plans launch (2025-12-02); Redshift remains Reserved-Node-only&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/ec2/graviton/" rel="noopener noreferrer"&gt;https://aws.amazon.com/ec2/graviton/&lt;/a&gt; — current page: "up to 20% less cost" vs comparable x86; ARM64/Neoverse (40% figure was Graviton2-gen)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/compute-optimizer/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/compute-optimizer/pricing/&lt;/a&gt; — free; covers EC2/ASG/EBS/Lambda/ECS-Fargate (+RDS, idle-resource 2026); 14-day default, ~93-day with paid Enhanced Infrastructure Metrics&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/aws-cost-management/aws-cost-explorer/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/aws-cost-management/aws-cost-explorer/pricing/&lt;/a&gt; — rightsizing recs; hourly/resource granularity $0.01 per 1,000 usage records/mo&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/cur/latest/userguide/what-is-cur.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cur/latest/userguide/what-is-cur.html&lt;/a&gt; — CUR is most granular billing data, free to generate (pay only S3 storage), delivered via Data Exports&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/&lt;/a&gt; — $0.005/hr per public IPv4 since 2024-02-01, attached or not&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/cost-optimization-checks.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/awssupport/latest/user/cost-optimization-checks.html&lt;/a&gt; — Trusted Advisor cost checks (idle LBs, underutilised EBS, unassociated EIPs)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/premiumsupport/technology/trusted-advisor/" rel="noopener noreferrer"&gt;https://aws.amazon.com/premiumsupport/technology/trusted-advisor/&lt;/a&gt; — full cost-optimization checks require paid Support plan (Business+)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>costoptimization</category>
      <category>devops</category>
      <category>finops</category>
    </item>
    <item>
      <title>Building My Own Cloud</title>
      <dc:creator>Rajesh Medampudi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 15:51:35 +0000</pubDate>
      <link>https://dev.to/medampudi/building-my-own-cloud-2590</link>
      <guid>https://dev.to/medampudi/building-my-own-cloud-2590</guid>
      <description>&lt;p&gt;I rent six dedicated servers from a company in Germany. Together they have more cores, more memory, and more SSD than most production clusters I worked on a decade ago.&lt;/p&gt;

&lt;p&gt;I run my own Kubernetes on them. Not managed. Not EKS. Not GKE. The whole stack, from the immutable OS up to the workloads.&lt;/p&gt;

&lt;p&gt;People who hear this ask me why, and the question usually arrives in one of two tones. The dangerous tone is &lt;em&gt;"that's amazing, how do I do it"&lt;/em&gt;. The responsible tone is &lt;em&gt;"why on earth would you do that to yourself"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This post is for the second group.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the cloud is the right answer for almost everyone
&lt;/h2&gt;

&lt;p&gt;Let me get this out of the way honestly: by every conventional metric, I should be using the cloud.&lt;/p&gt;

&lt;p&gt;Managed Kubernetes has become genuinely good. EKS has dramatically improved over the last three years. GKE has always been better than people gave it credit for. The serverless options are mature. The serverless databases are mature. The observability is mature. The bill is predictable in the way a Tuesday is predictable.&lt;/p&gt;

&lt;p&gt;Self-hosting violates almost every assumption that makes a startup productive. Time is the most expensive resource you have. The cloud sells you abstractions that turn that time into product. Running your own substrate means the time goes into the substrate.&lt;/p&gt;

&lt;p&gt;If you are trying to ship a product to customers — go use the cloud. Stop reading this post. It will only confuse you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the cloud does not sell you
&lt;/h2&gt;

&lt;p&gt;Here is what the cloud will not sell you, even if you are willing to pay extra: control over your own roadmap.&lt;/p&gt;

&lt;p&gt;The cloud's roadmap is the cloud's. They decide which APIs deprecate. They decide which regions get the new feature. They decide what your egress bill looks like. They decide whether your monitoring vendor — sitting on top of their infrastructure — is allowed to charge you eight times what it would cost you to host the same software yourself. They decide whether the small ML company hosting your fine-tuned model gets acquired by someone with very different priorities than the founders had.&lt;/p&gt;

&lt;p&gt;Most of the time, none of this matters. The roadmap is fine. The bill is fine. The egress is fine. You do not think about it.&lt;/p&gt;

&lt;p&gt;Then one day a regulation, an acquisition, or a pricing change makes you think about it, and you realise that the abstraction you bought was not an abstraction. It was a contract. With one customer.&lt;/p&gt;

&lt;p&gt;I bought my own substrate for the same reason I write on my own blog instead of posting on a single platform. Not because the platform is bad — but because the platform is not mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually run
&lt;/h2&gt;

&lt;p&gt;The shape of the thing, layer by layer, told plainly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hardware.&lt;/strong&gt; Six dedicated servers at a Hetzner data centre. Bare metal — not VMs, not "instances." Each node has multiple cores, real NVMe, real network bandwidth. The bill is a fraction of what equivalent compute on AWS would cost, and most months I am using a fraction of &lt;em&gt;that&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The OS.&lt;/strong&gt; Talos Linux on every node. Immutable, API-driven, no SSH. You do not log into Talos boxes; you reconcile them. After running mutable Linux for fifteen years, the experience of &lt;em&gt;"I cannot break this server even if I try"&lt;/em&gt; is psychologically corrective. I wish I had switched sooner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Kubernetes substrate.&lt;/strong&gt; A multi-tenant Kubernetes distribution that gives me per-tenant control planes, per-tenant ingress, built-in storage, built-in observability, and a real package model. I treat tenants as isolation boundaries — one for each environment or product surface I want to keep separate. This is the layer that took me longest to understand and pays the most consistently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serverless.&lt;/strong&gt; Knative on top of the substrate, with resource patches tuned aggressively for the cluster size. Cold starts matter on a small cluster, so I rewrote four Python services in Go specifically because the cold starts mattered for my AI workloads. There is a separate post about that rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Durable workflows.&lt;/strong&gt; Temporal, exposed inside one tenant and bridged into another via a selector-based service so the UI works through the cluster's ingress. Replaced what would otherwise have been a sprawl of SQS-and-Lambda glue with a single workflow engine that survives restarts and treats retries as a first-class concept.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets.&lt;/strong&gt; OpenBAO — a Vault drop-in — with AppRole auth scoped per service. Every service that needs a credential gets one through OpenBAO. Nothing lives in environment variables in a Helm values file. Rotating a credential is a single operation, not an archaeology dig.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM gateway.&lt;/strong&gt; LiteLLM in front of a small fleet of model providers, with per-token pricing tiers configured explicitly so my AI agents have a budget. When an agent goes off the rails — and they do — the budget is the seatbelt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backups.&lt;/strong&gt; Velero, snapshotting the platform on a schedule. I restored from one of these snapshots in anger exactly once, which was enough to convert me from &lt;em&gt;"backups are a chore"&lt;/em&gt; to &lt;em&gt;"backups are oxygen."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edge.&lt;/strong&gt; A Nostr relay running on Cloudflare Workers, handling globally distributed traffic for a fraction of what a VPS would cost. The edge does what the edge is good at; the metal does what the metal is good at. The discipline is figuring out which is which.&lt;/p&gt;

&lt;p&gt;That is the substrate. Everything else I build — the personal blog, the family Bitcoin wallet, the consulting tools, the AI agents — runs on this stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistakes
&lt;/h2&gt;

&lt;p&gt;Three I will admit to in public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first one was treating Kubernetes like AWS.&lt;/strong&gt; I tried to recreate cloud primitives one for one. NAT Gateways. Per-tenant load balancers. Internet egress proxies. Each of these has a Kubernetes-native equivalent that is &lt;em&gt;better&lt;/em&gt; than the cloud version, and I spent two months reinventing things that were already in the platform's package catalogue. If the platform ships an opinion, take the opinion before you write a flag to override it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second was undersizing the control plane.&lt;/strong&gt; When you run nested control planes — child clusters whose API servers are pods on the parent — you can absolutely starve them by being stingy with CPU. A child cluster whose API server is being CPU-throttled looks indistinguishable from a child cluster that is just slow. I lost a weekend tracing a problem that turned out to be a &lt;code&gt;requests.cpu: 100m&lt;/code&gt; set by a copy-paste two months earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The third was ignoring the runbook.&lt;/strong&gt; I wrote a Day-2 operations runbook early. Excellent. Then I did not update it for six months. Excellent operational hygiene right up until the morning I had to follow it, and discovered the version I was following described a cluster topology I had since changed. The runbook is not a write-once artefact. It is a contract you renew every time the cluster moves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually costs
&lt;/h2&gt;

&lt;p&gt;The bill, all in: a couple hundred dollars a month for the bare metal. A small fraction of that for the edge bits and DNS. The Cloudflare side is free at my volume.&lt;/p&gt;

&lt;p&gt;The same workload on AWS — managed control plane, NAT Gateways, egress, observability, secrets — would cost me five to ten times more. I have done the math. I keep doing the math. The math keeps coming out the same.&lt;/p&gt;

&lt;p&gt;But the &lt;em&gt;real&lt;/em&gt; cost is not the bill. The real cost is the time I spend keeping it running. Some weeks that is an hour. Some weeks it is a weekend. Some weeks it is the entire weekend, and I think dark thoughts about EKS at two in the morning.&lt;/p&gt;

&lt;p&gt;Average it out and the time cost is significant — easily worth more than the bill differential at any honest hourly rate. So if you only care about &lt;em&gt;cash plus time&lt;/em&gt;, this is a bad trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you absolutely should not do this
&lt;/h2&gt;

&lt;p&gt;You should not do this if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are building a product and your customers are not yet sure they want it. Self-hosting will eat the runway you need to find product-market fit.&lt;/li&gt;
&lt;li&gt;You do not enjoy infrastructure. This is a hobby that pays in skill, not in money. If you do not like the hobby, you will resent every minute of it.&lt;/li&gt;
&lt;li&gt;You do not have a fallback. The cluster will go down. You need to be okay with that, or you need someone else who is.&lt;/li&gt;
&lt;li&gt;You think it will be cheaper without budgeting for time. It is not cheaper if you bill yourself honestly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: do this if and only if the &lt;em&gt;learning&lt;/em&gt; is worth the cost on its own, separate from the workload it hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I keep running it anyway
&lt;/h2&gt;

&lt;p&gt;A platform you control is a platform that will host whatever you decide to build next.&lt;/p&gt;

&lt;p&gt;I have a Bitcoin wallet I am building for my family. I have a content engine that publishes to a personal blog. I have a Frappe ERP I deploy for consulting clients. I have AI agents that need to run cheaply and reliably. I have a CLI that searches forty-seven thousand vectors of my own writing in under a second.&lt;/p&gt;

&lt;p&gt;None of these existed when I built the cluster. The cluster was built so that they &lt;em&gt;could&lt;/em&gt; exist when I needed them — on terms I controlled, with no third party between me and the workload.&lt;/p&gt;

&lt;p&gt;That is the deepest difference between renting your substrate and building one. The rented substrate is a contract. The substrate you built is an option — a low-cost option, with a long expiry, on whatever you decide to ship next.&lt;/p&gt;

&lt;p&gt;I will keep paying my Hetzner bill and my Talos curiosity tax for as long as I am building things. The day I stop is the day I migrate to managed Kubernetes and admit it.&lt;/p&gt;

&lt;p&gt;That day is not today.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>selfhosting</category>
      <category>devops</category>
      <category>infrastructure</category>
    </item>
  </channel>
</rss>
