Series: How I Used Claude Code to Slash My Cloud Bill by 63%

The Before Architecture

Here’s what I was running before this project started:

                          THE OLD WAY ($198/month)

Internet Traffic
      │
      ▼
┌─────────────┐
│  Cloudflare  │  (DNS only, no proxy features used)
│   DNS        │
└──────┬──────┘
       │
       ▼
┌──────────────────┐
│  DO App Platform  │  $10/mo
│  (reverse proxy)  │
└──────┬───────────┘
       │
       ▼
┌──────────────────┐
│  DO Load Balancer │  $12/mo
└──────┬───────────┘
       │
       ▼
┌──────────────────────────────────────────────────┐
│          DigitalOcean Kubernetes Cluster          │
│         3x s-2vcpu-8gb nodes ($168/mo)           │
│                                                  │
│  ┌─────────────────┐  ┌──────────────────────┐   │
│  │ reverse-proxy    │  │ nginx StatefulSet    │   │
│  │ StatefulSet      │  │ (2 pods + git-sync)  │   │
│  │ (3 nginx pods)   │  │ serving static files │   │
│  └────────┬────────┘  └──────────────────────┘   │
│           │                                      │
│           ▼                                      │
│  ┌─────────────────┐  ┌──────────────────────┐   │
│  │ n8n              │  │ my-fleet namespace   │   │
│  │ (1 pod)          │  │ (FleetDM - orphaned) │   │
│  └─────────────────┘  │ 32GB PVCs sitting     │   │
│                       │ there doing nothing   │   │
│                       └──────────────────────┘   │
│                                                  │
│  + Professional Container Registry ($20/mo)      │
│  + Block Storage ($5/mo)                         │
│  + Tax ($11/mo)                                  │
└──────────────────────────────────────────────────┘

Total: $198/month

The traffic path for a static site was absurd: Cloudflare DNS resolved to the App Platform, which forwarded to the Load Balancer, which hit one of three reverse-proxy nginx pods, which proxied to one of two nginx StatefulSet pods serving static files from a git-sync sidecar. Five hops to serve an HTML page.

And then there was the my-fleet namespace – a FleetDM deployment I’d been testing months ago and never cleaned up. It left behind 32GB of Persistent Volume Claims that were just sitting there, attached to nothing, costing money.

The After Architecture

                         THE NEW WAY ($74/month)

Static Sites (7 Hugo sites + 2 others)
      │
      ▼
┌──────────────────┐
│  Cloudflare Pages │  $0/mo (free tier)
│  9 projects       │
│  Built by GH      │
│  Actions on push   │
└──────────────────┘


n8n (workflow automation)
      │
      ▼
┌──────────────────┐     ┌────────────────────┐
│  Cloudflare Edge  │────▶│ Cloudflare Tunnel  │  $0/mo
└──────────────────┘     └────────┬───────────┘
                                  │
                                  ▼
┌──────────────────────────────────────────────────┐
│          DigitalOcean Kubernetes Cluster          │
│         3x s-1vcpu-2gb nodes ($63/mo)            │
│                                                  │
│  ┌─────────────────┐  ┌──────────────────────┐   │
│  │ cloudflared      │  │ tailscale-subnet-    │   │
│  │ (1 pod, TF)      │  │ router (1 pod, TF)   │   │
│  └────────┬────────┘  └──────────────────────┘   │
│           │                       ▲               │
│           ▼                       │               │
│  ┌─────────────────┐    Tailscale mesh            │
│  │ n8n              │    (WireGuard)              │
│  │ (1 pod)          │         │                   │
│  └─────────────────┘         │                   │
│                              │                   │
│  + Basic Container Registry ($5/mo)              │
│  + Minimal Storage ($2/mo)                       │
│  + Tax ($4/mo)                                   │
└──────────────────────────────────────────────────┘
                               │
                      Tailscale mesh
                               │
                               ▼
                    ┌──────────────────┐
                    │ Raspberry Pi 5    │
                    │ OpenWrt Router    │  (home network)
                    │ + adblock         │
                    │ + nlbwmon         │
                    │ + Tailscale       │
                    └──────────────────┘

Total: $74/month

Static sites go directly to Cloudflare Pages. No Kubernetes involvement at all. n8n goes through a Cloudflare Tunnel that terminates inside the cluster. Private access to K8s services goes through Tailscale’s WireGuard mesh. The home network runs on a Raspberry Pi 5 with OpenWrt, ad blocking, and Tailscale.

What Was Deleted

Here’s the full list of infrastructure that no longer exists:

Resource What It Was Monthly Cost
nginx StatefulSet 2 pods + git-sync sidecars serving static files (node resources)
reverse-proxy StatefulSet 3 nginx pods routing traffic to backends (node resources)
DO App Platform Frontend reverse proxy $10
DO Load Balancer Layer 4 load balancer $12
my-fleet namespace Orphaned FleetDM deployment (32GB PVCs)
ubuntu utility pod Debug pod that was always running (node resources)
Old Hugo GH Actions workflow Built and pushed to container registry (registry storage)
Professional Container Registry Storing images we no longer need $20

The three 8GB nodes were downsized to three 2GB nodes. With the nginx StatefulSet, reverse-proxy StatefulSet, FleetDM, and utility pod gone, the cluster is running about 60% utilization on the smaller nodes. Plenty of headroom for n8n, cloudflared, tailscale, and the supporting services (Redis, SearXNG).

What Was Created

Resource Purpose Monthly Cost
9 Cloudflare Pages projects Static site hosting for all Hugo sites + 2 others $0
1 Cloudflare Tunnel Secure ingress for n8n $0
1 cloudflared Deployment Tunnel connector inside K8s (terraform) $0
1 Tailscale subnet router Private mesh access to K8s ClusterIPs (terraform) $0
GitHub Actions workflow Builds and deploys all Hugo sites to CF Pages $0

Everything new is either free-tier SaaS or a lightweight pod managed by Terraform.

The Numbers

BEFORE                              AFTER
──────                              ─────
K8s Nodes (3x 8GB):    $168.00     K8s Nodes (3x 2GB):     $63.00
Pro Container Registry:  $20.00     Basic Container Registry: $5.00
DO App Platform:         $10.00     (deleted)
DO Load Balancer:        $12.00     (deleted)
Block Storage:            $5.00     Block Storage:            $2.00
Tax:                     $11.00     Tax:                      $4.00
                       ────────                             ────────
TOTAL:                 $198.00/mo   TOTAL:                  $74.00/mo

SAVINGS: $124.00/month = $1,488.00/year

63% reduction. And the architecture is simpler. Fewer moving parts, fewer things that can break, fewer things to monitor.

Everything That Broke

This wasn’t a smooth ride. Here’s every problem we hit, in roughly chronological order:

1. Hugo Eating HTML Comments

When building the static sites, Hugo was stripping HTML comments from the output. One site used conditional comments for IE compatibility (yes, in 2026 – legacy corporate intranet stuff). The fix was adding [markup.goldmark.renderer] unsafe = true to the Hugo config and using shortcodes instead of raw HTML comments.

2. dirname vs cut in the GitHub Actions Workflow

The workflow needed to extract the site name from the changed file path. My first attempt used dirname but it returned the full relative path. Switched to cut -d/ -f1 which worked but broke when the path had a different structure. Eventually settled on a combination that handled all cases:

echo "$file" | cut -d'/' -f1

3. Cloudflare Pages UI Missing the Project

After creating a Pages project via wrangler, it didn’t show up in the Cloudflare dashboard for about 10 minutes. I thought the creation had failed and created a duplicate. Turns out the dashboard has a propagation delay for API-created projects.

4. AdGuard Home Port 53 Conflict

Covered in Part 4. AdGuard tried to bind port 53 but dnsmasq already had it. Replaced with OpenWrt’s native adblock package.

5. Cloudflare Tunnel Needing CF-Proxied DNS

Covered in Part 3. The tunnel CNAME only works when the DNS record is proxied through Cloudflare (orange cloud). DNS-only records (grey cloud) can’t resolve *.cfargotunnel.com because that domain only exists within Cloudflare’s proxy layer.

6. Wrangler OAuth Tokens Expiring

The OAuth tokens from wrangler login expired every 30-60 minutes, breaking API calls mid-session. We kept having to refresh them or re-authenticate. Eventually created a proper API token in the dashboard. Lesson: use API tokens for automation, not OAuth session tokens.

7. 51MB DNG File Blocking Deploy

One of the Hugo sites had a 51MB DNG (digital negative) raw photo file committed to the repo. Cloudflare Pages has a 25MB per-file limit. The deploy failed silently – the build succeeded but the publish step errored. Found it by checking the deploy logs, removed the DNG, and added *.dng to .gitignore.

The Raspberry Pi 5 Bonus Project

The Pi router wasn’t part of the original plan. I started optimizing my cloud bill and ended up building my own network infrastructure. But it ties together:

  • The Pi runs Tailscale, connecting my home network to the K8s cluster
  • Ad blocking at the DNS level means cleaner browsing without per-device configuration
  • Bandwidth monitoring via nlbwmon gives me visibility into network usage
  • When the TP-Link EAP650 arrives, the Pi becomes the actual router (bridge mode on Comcast) with whole-house WiFi coverage

Total hardware cost was $175 one-time. It replaces the WiFi portion of the Comcast gateway, which I couldn’t separately itemize on the bill but was always mediocre.

Honest Assessment: Claude Code’s Strengths and Limits

I spent roughly two days of conversations with Claude Code across this entire project. Here’s what I observed.

What Claude Code Was Great At

Systematic auditing. The initial infrastructure audit in Part 1 was the single most valuable thing Claude did. It methodically inventoried every resource in my DigitalOcean account – nodes, volumes, load balancers, registries, app platform apps, DNS records – and cross-referenced them to find orphaned resources I’d forgotten about. A human would do this too, but it would take longer and I’d probably miss the FleetDM PVCs.

Parallel research. When I asked “what’s the best router software for a Pi 5”, Claude didn’t just give me one answer. It compared pfSense, OPNsense, IPFire, and OpenWrt across architecture support, stability, and package availability. It knew pfSense was x86-only without me having to discover that the hard way.

Remembering context across a complex multi-system migration. This project touched DigitalOcean, Cloudflare, GitHub Actions, Terraform, Kubernetes, OpenWrt, and Tailscale. Claude kept track of all of it – the DNS records we’d changed, the services we’d deleted, the tokens we’d created. When something broke, it could reason about the full system state instead of just the immediate error.

Terraform generation. The cloudflared and Tailscale Terraform files were generated correctly on the first try. RBAC roles, security contexts, environment variable references – the kind of Kubernetes boilerplate where a missing field means a cryptic error 10 minutes later.

What Still Needed Human Judgment

DNS cutover timing. Moving nameservers for shellnetsecurity.com from DigitalOcean to Cloudflare was the highest-risk operation in the project. Claude could execute the commands, but the decision of when to do it (middle of the day vs. evening, all at once vs. gradual) was mine. If mail delivery broke, that’s on me.

Deciding what to keep vs. delete. Claude identified that the FleetDM namespace was orphaned and the nginx StatefulSet was redundant. But the decision to actually delete them – knowing I couldn’t easily undo it – required understanding the business context. Was anyone depending on those FleetDM endpoints? Was there data in those PVCs I’d need later? Those were human calls.

The ad blocking impact on my wife’s TikTok. The adblock package on the Pi blocks ad domains at the DNS level. This broke some in-app experiences – TikTok’s “Shop” tab stopped loading because it depends on ad-network domains for product listings. I had to whitelist a couple of domains to keep the peace. Claude could help me find the blocked domain in the dnsmasq logs, but knowing that “my wife can’t shop on TikTok” is a higher priority than “zero ads” – that’s household politics, not engineering.

The Final Architecture Diagram

┌─────────────────────────────────────────────────────────────────────┐
│                        THE COMPLETE PICTURE                         │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  PUBLIC TRAFFIC                                                     │
│  ─────────────                                                      │
│                                                                     │
│  Static Sites ──▶ Cloudflare Pages (9 projects, free)               │
│       ▲                                                             │
│       │                                                             │
│  GitHub Actions builds Hugo on push                                 │
│                                                                     │
│  n8n ──▶ Cloudflare Edge ──▶ CF Tunnel ──▶ cloudflared ──▶ n8n pod │
│                                            (K8s, terraform)         │
│                                                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  PRIVATE TRAFFIC (Tailscale mesh, WireGuard)                        │
│  ───────────────                                                    │
│                                                                     │
│  ┌──────────┐    100.x.y.z mesh    ┌───────────────────┐           │
│  │ Pi 5     │◄────────────────────►│ K8s subnet router │           │
│  │ OpenWrt  │  advertises           │ (tailscale pod)   │           │
│  │ 192.168  │  192.168.1.0/24      │ advertises         │           │
│  │ .1.0/24  │                      │ 10.245.0.0/16     │           │
│  └────┬─────┘                      └───────────────────┘           │
│       │                                     │                       │
│       ▼                                     ▼                       │
│  Home WiFi                          K8s ClusterIPs                  │
│  (ShellNet-5G)                      (n8n, SearXNG, Redis, etc.)    │
│                                                                     │
│  Also on mesh: MacBook, iPad Pro                                    │
│                                                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  INFRASTRUCTURE                                                     │
│  ──────────────                                                     │
│                                                                     │
│  DigitalOcean K8s: 3x s-1vcpu-2gb nodes ............ $63/mo        │
│  DO Container Registry (Basic) ...................... $5/mo         │
│  DO Block Storage ................................... $2/mo         │
│  Tax ................................................ $4/mo         │
│  Cloudflare Pages ................................... $0            │
│  Cloudflare Tunnel .................................. $0            │
│  Tailscale (Personal) ............................... $0            │
│  ────────────────────────────────────────────────────────           │
│  TOTAL .............................................. $74/mo        │
│  SAVINGS vs. BEFORE ................................. $124/mo       │
│  ANNUAL SAVINGS ..................................... $1,488        │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

What I’d Do Differently

If I were starting over:

  1. Start with the audit. The audit should be step zero of any cloud cost project. I’d been paying for orphaned resources for months without knowing it.
  2. Use API tokens from the start. The wrangler OAuth token saga wasted at least an hour of debugging. Create proper scoped API tokens before starting any automation.
  3. Test DNS changes in a staging zone first. I moved production DNS during the project. If I had a test domain, I could have validated the Cloudflare Tunnel setup without risking mail delivery.
  4. Check file size limits before migrating. The 51MB DNG file could have been caught with a simple find . -size +25M before attempting the Cloudflare Pages deploy.

The Session

Roughly two days of conversations with Claude Code. Not two continuous days – more like scattered sessions over a week, maybe 15-20 hours of actual interaction. The context window was essential here. Each session built on the last, and Claude remembered what we’d already changed, what we’d already deleted, and what was still pending.

The hardest part wasn’t the technical work. It was the decision-making between sessions – reviewing what had been changed, thinking about what could go wrong, deciding whether to proceed with the next phase. The AI handled the execution. I handled the judgment calls.

Was It Worth It?

$1,488/year in savings. Simpler architecture. Better security posture (no public load balancer, Tailscale mesh for private access, ad blocking network-wide). A home router I actually control. And a much better understanding of my own infrastructure.

Yes. It was worth it.


This entire series was written with Claude Code based on a real terminal session. The infrastructure changes, the errors, and the fixes all happened exactly as described.