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

The Pi on the Desk

I had a Raspberry Pi 5 (8GB) sitting on my desk running Debian trixie. It was serving as an occasional dev box, but mostly collecting dust. Meanwhile, I was paying Comcast for a gateway that doubled as a mediocre WiFi router. The gateway WiFi was fine for my wife’s devices, but I started thinking: what if I ran my own router?

The home network is simple. About 10 devices on the main network – my machines, my wife’s laptop, the TV, a couple of tablets. Smart home stuff (cameras, plugs, thermostat) is all on a separate Verizon network and doesn’t touch my main LAN. So the routing requirements aren’t heavy. I didn’t need enterprise throughput. I needed control.

Choosing the Software: Why OpenWrt Won

I asked Claude to research the best firewall/router software that runs on a Raspberry Pi 5 in 2026. The short version:

  • pfSense: Doesn’t support ARM. Full stop. It’s x86-only and FreeBSD-based. Not happening on a Pi.
  • OPNsense: Experimental ARM support at best. The community has some builds floating around, but nothing official and nothing I’d trust as my primary router.
  • IPFire: Has ARM builds, but multiple reports of boot issues on the Pi 5 specifically. Something about the newer BCM2712 SoC and IPFire’s kernel version not playing nice.
  • OpenWrt: First-class Raspberry Pi 5 support. Official images. Active development. Massive package repository. The bcm27xx target has been stable for years.

OpenWrt it was.

Flashing and the First Problem

I downloaded the OpenWrt 25.12.2 image for the Pi 5 (openwrt-25.12.2-bcm27xx-bcm2712-rpi-5-ext4-factory.img.gz) and flashed it with dd:

gunzip openwrt-25.12.2-bcm27xx-bcm2712-rpi-5-ext4-factory.img.gz
sudo dd if=openwrt-25.12.2-bcm27xx-bcm2712-rpi-5-ext4-factory.img \
  of=/dev/rdisk4 bs=4m status=progress

Popped the SD card in, powered on. The Pi booted. Green LED was blinking. But I couldn’t reach it.

The problem was obvious once I thought about it: OpenWrt defaults to 192.168.1.1 on its LAN interface. My network runs on 10.0.0.x. The Pi was sitting there on 192.168.1.1 with no route back to my subnet, and my Mac had no interface in the 192.168.1.0/24 range.

The Connectivity Hack

I needed to reach 192.168.1.1 from my Mac without changing the Mac’s primary network config. The trick is adding an IP alias:

sudo ifconfig en0 alias 192.168.1.2 netmask 255.255.255.0

This gives en0 a second IP address in the 192.168.1.0/24 subnet without disrupting the existing 10.0.0.x address. Now I could reach the Pi:

BusyBox v1.36.1 (2025-12-15 14:22:38 UTC) built-in shell (ash)

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 OpenWrt 25.12.2, r28597-a9a88ccf01
 -----------------------------------------------------

I was in.

Configuring the Interfaces

The plan: eth0 connects to the Comcast gateway via Ethernet and gets a DHCP address (WAN). The built-in WiFi (phy0-ap0) acts as the LAN access point for my devices. Simple two-interface router.

First, I set up eth0 as WAN:

uci set network.wan=interface
uci set network.wan.device='eth0'
uci set network.wan.proto='dhcp'
uci commit network

Then the WiFi AP on the LAN side:

uci set wireless.radio0.disabled='0'
uci set wireless.radio0.channel='36'
uci set wireless.radio0.htmode='VHT80'
uci set wireless.radio0.band='5g'

uci set wireless.default_radio0.ssid='ShellNet-5G'
uci set wireless.default_radio0.encryption='sae-mixed'
uci set wireless.default_radio0.key='[redacted]'
uci set wireless.default_radio0.network='lan'

uci commit wireless
wifi reload

I chose 5GHz because my office is small and every device I care about supports it. The range tradeoff vs 2.4GHz doesn’t matter when everything is within 15 feet.

Why Channel 36?

Before committing to a channel, I ran a WiFi scan to see what the neighbors were doing:

iwinfo phy0-ap0 scan
Cell 01 - Address: A4:91:B1:xx:xx:xx
          ESSID: "XFINITY"
          Mode: Master  Channel: 149 (5.745 GHz)
          Signal: -72 dBm

Cell 02 - Address: 68:D7:9A:xx:xx:xx
          ESSID: "ATT-WIFI-5G"
          Mode: Master  Channel: 44 (5.220 GHz)
          Signal: -81 dBm

Cell 03 - Address: 9C:53:22:xx:xx:xx
          ESSID: "NETGEAR82"
          Mode: Master  Channel: 48 (5.240 GHz)
          Signal: -78 dBm

Cell 04 - Address: 20:E5:2A:xx:xx:xx
          ESSID: "Verizon_5G_Home"
          Mode: Master  Channel: 149 (5.745 GHz)
          Signal: -69 dBm

Channels 44, 48, and 149 all had neighbors. Channel 36 was completely clear. No brainer.

After reloading, the AP came up:

iwinfo phy0-ap0 info
phy0-ap0  ESSID: "ShellNet-5G"
          Access Point: DC:A6:32:xx:xx:xx
          Mode: Master  Channel: 36 (5.180 GHz)
          Tx-Power: 20 dBm  Link Quality: 70/70
          Signal: -33 dBm
          Bit Rate: 433.3 MBit/s
          Encryption: mixed WPA2/WPA3 PSK/SAE (CCMP)

433 Mbps link rate on VHT80. Three clients connected within minutes – my MacBook, my iPad, and my wife’s laptop.

The AdGuard Home Disaster

With the router working, I wanted ad blocking at the DNS level. AdGuard Home seemed like the obvious choice – I’d used it before on other machines. I installed it:

opkg update
opkg install adguardhome
/etc/init.d/adguardhome start

It crashed immediately. The logs told me why:

2026/03/28 14:32:17.441 [fatal] Couldn't start AdGuard Home
2026/03/28 14:32:17.441 [fatal] listen tcp 192.168.1.1:53: bind: address already in use

Port 53 was already taken by dnsmasq, which is OpenWrt’s built-in DNS resolver and DHCP server. AdGuard Home wants to own port 53 completely. You can reconfigure one or the other, but it gets ugly fast – dnsmasq handles DHCP lease assignment and DNS forwarding for the whole LAN, and ripping it out means reconfiguring a lot of plumbing.

I removed AdGuard:

opkg remove adguardhome

The Better Solution: adblock

OpenWrt has its own ad blocking package that works with dnsmasq instead of fighting it:

opkg install adblock luci-app-adblock
/etc/init.d/adblock start

adblock downloads blocklists and feeds them directly into dnsmasq as null routes. No port conflict, no separate DNS server, no extra process eating RAM on a device with limited resources.

After enabling the default blocklists and letting it sync:

/etc/init.d/adblock status
::: adblock runtime information
  + adblock_status  : enabled
  + adblock_version : 4.2.1
  + blocked_domains : 295031
  + active_sources  : adaway, disconnect, yoyo, stevenblack
  + dns_backend     : dnsmasq
  + last_run        : 2026-03-28 15:01:22

295,031 domains blocked. Ads gone across every device on the network without installing anything on the clients.

Fixing Upstream DNS

By default, OpenWrt was using whatever DNS servers the Comcast gateway handed out via DHCP – which was 75.75.75.75 and 75.75.76.76, Comcast’s own resolvers. These are known for being slow and occasionally doing NXDOMAIN hijacking (redirecting failed lookups to a Comcast search page).

I switched to Cloudflare and Google:

uci set network.wan.peerdns='0'
uci set network.wan.dns='1.1.1.1 8.8.8.8'
uci commit network
/etc/init.d/network restart

peerdns='0' tells the WAN interface to ignore the DNS servers from DHCP and use the ones I specify. Instant improvement in resolution times.

Bandwidth Monitoring

I wanted to see which devices were using the most bandwidth. OpenWrt has a package for that:

opkg install nlbwmon luci-app-nlbwmon
/etc/init.d/nlbwmon start
/etc/init.d/nlbwmon enable

nlbwmon (Netlink Bandwidth Monitor) tracks per-host bandwidth usage and stores it by day and month. The LuCI web interface shows nice tables and graphs. Within a day, I could see that my MacBook was pulling 40GB+ from pulling container images and running large model downloads, while the TV was a distant second with streaming.

Hardware on Order

The Pi 5’s built-in WiFi works, but it’s a single antenna with limited range. For the office, it’s fine. For the rest of the house, I need more coverage. I ordered:

  • TP-Link EAP650 – WiFi 6 AX3000 access point, $80 on sale. This will handle whole-house coverage. It supports OpenWrt-compatible VLANs and can be managed as a dumb AP.
  • USB Gigabit Ethernet adapter – $15 on Amazon. This gives the Pi a second physical Ethernet port so I can run a proper WAN on one and LAN on the other, instead of sharing eth0 for WAN and relying on WiFi for LAN.

The Future Plan

Once the hardware arrives:

  1. Bridge mode on Comcast: Put the Comcast gateway into bridge mode so it’s just a modem. The Pi becomes the actual router handling NAT, firewall, and DHCP.
  2. EAP650 for whole-house coverage: The access point goes in the hallway, wired back to the Pi. 5GHz and 2.4GHz for full house coverage.
  3. Pi built-in WiFi for the office: Keep ShellNet-5G as a dedicated office SSID for my workstation and dev devices.
  4. Tailscale integration: More on this in Part 5, but the Pi running OpenWrt becomes the gateway into my home network from anywhere.

The total hardware cost: $80 (Pi 5) + $80 (EAP650) + $15 (USB Ethernet) = $175 one-time to replace a Comcast gateway WiFi that was never great.

What Claude Code Did Here

This was a different kind of session than the cloud migration. Instead of running CLI commands against cloud APIs, I was asking Claude to help me research, plan, and troubleshoot a hardware project. It:

  • Compared router software options and identified the ARM support problem with pfSense/OPNsense immediately
  • Knew the ifconfig alias trick for reaching the Pi on a different subnet
  • Diagnosed the AdGuard port 53 conflict and recommended adblock as the OpenWrt-native alternative
  • Helped me understand the WiFi channel scan output and pick the least congested channel

Not every problem needs an AI to solve it. But having something that can hold the context of “I have a Pi 5, running OpenWrt, connected to a Comcast gateway on 10.0.0.x, and I want ad blocking that doesn’t conflict with dnsmasq” and give targeted answers – that’s genuinely useful.

Next up in Part 5: connecting the home network and the Kubernetes cluster into a single private mesh with Tailscale.


My wife asked me why the internet was down. I told her I was “upgrading the network.” She said “the old one worked fine.” She’s not wrong. But now it works fine AND I can see which device is using all the bandwidth. Knowledge is power.