Series: How I Used Claude Code to Slash My Cloud Bill by 63%
- Part 1: The $198/Month Wake-Up Call
- Part 2: Migrating 7 Static Sites to Cloudflare Pages
- Part 3: Killing the Load Balancer with Cloudflare Tunnel
- Part 4: Building a Home Router with a Raspberry Pi 5 (you are here)
- Part 5: Connecting Everything with Tailscale
- Part 6: The Final Architecture - $74/mo and Zero Regrets
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
bcm27xxtarget 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:
- 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.
- 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.
- Pi built-in WiFi for the office: Keep
ShellNet-5Gas a dedicated office SSID for my workstation and dev devices. - 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 aliastrick for reaching the Pi on a different subnet - Diagnosed the AdGuard port 53 conflict and recommended
adblockas 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.