DevOps & Homelab Adventures

Welcome! I document my journey learning DevOps, building homelabs, and automating everything.

  • Journal: Weekly learning logs from my DevOps journey
  • Projects: Deep dives into homelab builds and AD labs
  • Docs: Runbooks and reference guides
  • Reading: What I’m learning from books and courses

Subscribe via RSS to follow along.

IP Registration Lessons: IPv6, TTL Auto-Renewal, and SSO That Wasn't Worth It

After deploying the self-service IP registration system for family Jellyfin access, three things came up within the first day of real-world testing. IPv6 Privacy Extensions Break Registration The first family member to register got an IPv6 address. The Worker stored it with a /128 (exact match), but when she visited media.8devops.com, her phone used a different IPv6 address. IPv6 privacy extensions rotate the interface identifier (the last 64 bits) on every connection to prevent tracking. ...

April 20, 2026 · 3 min · Adam Behn

Self-Service IP Registration for Family Jellyfin Access

After setting up Cloudflare Tunnel with Zero Trust Access for Jellyfin, I hit a new problem: family members with Rokus and Apple TVs outside my network couldn’t get through the email OTP gate. Streaming device apps can’t render a Cloudflare login page or enter an OTP code. The Problem Cloudflare Access works great for browsers. But Jellyfin client apps on Rokus, Apple TVs, and phones make direct API calls. They need to reach Jellyfin without a browser-based auth step in the middle. ...

April 19, 2026 · 4 min · Adam Behn

Exposing Jellyfin Through Cloudflare Tunnel with Zero Trust Access

I needed to access my Jellyfin media server from a managed work laptop where I can’t install Tailscale or any VPN client. Cloudflare Tunnel solved this: outbound-only connection from the LXC, no open firewall ports, and a Zero Trust email OTP gate before anyone can reach Jellyfin. The Problem My Jellyfin instance runs in an LXC container on Proxmox. It’s accessible over Tailscale from my personal devices, but some environments have endpoint protection that blocks VPN installs. I needed a way to access my media library over plain HTTPS without installing anything on the client. ...

April 18, 2026 · 4 min · Adam Behn

Retiring pve005: Decommissioning a Proxmox Node the Hard Way

Why Now pve005 was an i5-7500 with 16GB of RAM. It ran the original Jellyfin LXC with 1.1TB of media on a local ZFS pool. Once I rebuilt the lab around a 3-node Ryzen 9 cluster with Ceph storage, pve005 became dead weight. The media was migrated to jellyfin01 on the new cluster months ago. The old LXC was stopped. pve005 was still drawing power, still in the Ceph quorum, and still showing up in every Ansible run. ...

April 8, 2026 · 4 min · Adam Behn

Moving a Raspberry Pi Offsite: Everything That Went Wrong

The Plan Move pi-burg, my Raspberry Pi 3 running restic as an offsite backup target, from my house to my mom’s. Simple, right? It already had Tailscale, so once it was on her wifi it would just appear on the tailnet from wherever. Total estimated time: 15 minutes. Total actual time: ~6 hours. The Stack Raspberry Pi 3 running Raspberry Pi OS Lite 8TB USB drive with an existing restic repository Tailscale for tailnet connectivity (no static IPs, no port forwarding) A long drive to mom’s house What Was Supposed to Happen Configure wifi on the Pi for mom’s network before leaving home Drive to mom’s Plug in power, let Tailscale come up Profit What Actually Happened Chapter 1: Cloud-init Is a Liar The Pi was already provisioned, so I edited /boot/firmware/network-config from my Mac to add mom’s wifi. I even recomputed the WPA PSK hash because the stored one was for a different SSID. Saved, ejected, booted. ...

April 4, 2026 · 6 min · Adam Behn

Self-Hosting OpenMemory MCP on Proxmox with Tailscale

The Goal I wanted a persistent AI memory layer — something that stores context across conversations and tools, accessible from Claude Desktop, Claude Code, and eventually other MCP clients. The official mem0 platform exists, but I wanted to self-host it on my Proxmox cluster for control and privacy. The Stack The deployment runs on mem01 (LXC 3003, pve02) with three Docker containers: Ollama — LLM inference for embeddings (bge-m3) and chat (qwen3:8b) OpenMemory — the MCP server itself, using SQLite for vectors and metadata Open WebUI — optional web interface for testing I started with mem0-mcp-selfhosted (Neo4j + Qdrant + Python SDK) but it crashed repeatedly and had a painful dependency chain. OpenMemory — SQLite for everything, Node.js SDK — just worked. ...

March 31, 2026 · 4 min · Adam Behn

Running 6 Minecraft Servers on Proxmox with Docker-in-LXC

The Goal The kids want Minecraft servers. Not one — six. Survival, Creative, Adventure, Minigames, Hardcore, and a modded Fabric server. I have three Ryzen 9 5900X Proxmox nodes with 64-128GB RAM each, so hardware isn’t the problem. The question was how to deploy and manage them without it becoming a second job. The Stack After researching management panels (Pterodactyl, Crafty, AMP, MCSManager), I landed on the simplest approach: Docker Compose with the itzg/minecraft-server image. It handles Paper builds, EULA acceptance, Aikar’s JVM flags, RCON, and graceful shutdown — all via environment variables. No panel needed. ...

March 29, 2026 · 4 min · Adam Behn

AdGuard Home on Proxmox with EdgeRouter X-SFP: DNS, Ad Blocking, and Reverse DNS

The Problem My homelab has multiple VLANs, a Tailscale overlay network, and no ad blocking. DNS was handled entirely by the EdgeRouter X-SFP forwarding to Cloudflare. I wanted: Network-wide ad blocking without per-device configuration Conditional DNS forwarding so LAN clients can resolve Tailscale hostnames Non-Tailscale devices (Rokus, smart TVs) able to reach Tailscale services like Jellyfin Client names on the DNS dashboard instead of raw IPs The Architecture 1 2 3 4 5 6 7 8 9 10 11 12 13 LAN Clients (10.150.10.0/24) │ ├─ DNS ──→ dns01 (AdGuard Home, 10.150.60.11) │ ├─ Upstream: Cloudflare DoH, Google DoH │ ├─ Conditional: *.<tailnet>.ts.net → 100.100.100.100 │ └─ Reverse DNS: PTR queries → EdgeRouter (10.150.10.1) │ ├─ DHCP ──→ EdgeRouter X-SFP (dnsmasq) │ ├─ Primary DNS: 10.150.60.11 (AdGuard) │ ├─ Secondary DNS: 10.150.10.1 (router fallback) │ └─ Search domain: <tailnet>.ts.net │ └─ 100.x.x.x traffic ──→ static route → dns01 → MASQUERADE → tailscale0 dns01 is an LXC container on pve01 running AdGuard Home and acting as a Tailscale subnet router advertising all four LAN subnets. ...

March 25, 2026 · 4 min · Adam Behn

Rebuilding My Homelab: From 9 Nodes to 3 with Proxmox 9 and Ceph

The Problem My homelab had grown organically into a 9-node Proxmox 8 cluster on a flat 10.150.10.0/24 network. Six i5/i7 machines (pve001-006) with 16GB RAM each, plus three Ryzen 9 5900X machines (pve007-009) with 64-128GB RAM and dedicated GPUs. The old nodes were underpowered, the network was a mess, and managing it all was getting painful. It was time to consolidate. The Plan Rebuild the three Ryzen machines as a proper 3-node cluster with: ...

March 7, 2026 · 6 min · Adam Behn

Setting Up Tiered Ceph Storage with CephFS and RBD on Proxmox 9

The Setup I have a 3-node Proxmox 9 cluster, each with a 4TB SSD and 2TB HDD dedicated to Ceph. The NVMe drives stay local for fast VM storage. The question was: how do I use the SSDs for performance-sensitive workloads and the HDDs for bulk storage? 1 2 3 4 Per Node: 2TB NVMe → nvme-local (LVM-thin, not Ceph) 4TB SSD → Ceph OSD (fast tier) 2TB HDD → Ceph OSD (bulk tier) CRUSH Rules: Telling Ceph Where to Put Data Ceph already knows which OSDs are SSDs and which are HDDs — it assigns device classes automatically. But by default, it’ll spread data across all OSDs regardless of type. CRUSH rules let you pin pools to specific device classes. ...

March 7, 2026 · 5 min · Adam Behn