Everything I've got running β 35 containers across three VMs on a Proxmox host, a NAS, and one device I put together myself. Some of it took way longer than it should have. Tap read the story on any card for the diagram and the actual context.
01
ποΈ
Delos β DIY Voice-Note Device
A handheld voice-note device I specced, assembled and debugged β a Waveshare ESP32-S3 board with a 1.54" e-paper screen in a 3D-printed case. I specced it, built it and debugged it on real hardware; Claude wrote the firmware and the server side with me directing it. Hold record, speak, and the audio goes over WiFi to a FastAPI service on the homelab, gets transcribed by a self-hosted Whisper container, cleaned up and titled by a single Claude call, and written into my Obsidian vault. Double-tap to ask a question and the answer renders on the e-paper. Firmware updates itself over the air with bootloader-enforced rollback.
[ hold REC ] ββΆ 16kHz mono wav ββΆ WiFi
β
voice-notes (FastAPI, token auth)
β
Whisper (faster-whisper, int8, local)
β transcript
claude -p ββ¬β title + cleanup
ββ tags + [[wikilinks]]
ββ tasks + reminders
β
Obsidian vault ββΆ laptop + phone (LiveSync)
sleep screen βββ server-rendered panel (agenda Β· tasks Β· battery)
new firmware ββΆ device pulls on next sync ββΆ rollback if it won't boot
This is the project I didn't expect to build. I wanted something I could talk to without picking up my phone, because picking up my phone means twenty minutes gone. So Delos is deliberately limited: it captures, and it never hands you anything back to scroll.
How it got made: Claude wrote the code, I directed it. Mine are the idea, the hardware, every call about what this should and shouldn't be, and the part where it's on the desk not working and someone has to figure out why. I've killed finished features for making it too much like a phone β that filter is the project.
Hardware is a Waveshare ESP32-S3 with a 1.54" e-paper display in a case I printed. Hold the button, talk, let go. The clip goes to a FastAPI container on the lab, Whisper transcribes it locally β no cloud speech service β and one Claude call cleans up the rambling, titles it, tags it, links it to related notes and pulls out any to-dos. It's in my vault and on my laptop before the device is back in my pocket.
The screen isn't wasted while it sleeps. The server renders an ambient panel β today's calendar, open tasks, battery β and the device just draws whatever it's handed. That was the rule the whole way through: dumb device, smart server. Anything I want to change about what it shows is a Python edit and a container restart, not a reflash.
It also updates itself. I publish new firmware, the device notices on its next sync, flashes it, and the bootloader rolls it back on its own if the new image won't boot β which has already saved me. Double-tapping record asks Claude a question out loud and the answer shows up on the e-paper in about twelve seconds.
Most of the lessons were budgets I didn't know I had. E-paper is free to hold an image but the radio isn't, so every sync is battery. And a 200-pixel-wide screen will quietly eat the end of any line you didn't actually measure in the font you're rendering with.
02
π¬
Self-Hosted Media Stack
Full *arr stack: Jellyfin for streaming, Radarr + Sonarr for automated movie/TV downloads, Prowlarr for indexer management (11 indexers), qBittorrent tunneled through Gluetun VPN, Bazarr for subtitles, Unpackerr for archive extraction.
you ββΆ Radarr / Sonarr ββΆ "I want this"
β
βββββββ΄ββββββ
Radarr Sonarr β the *arrs
βββββββ¬ββββββ
Prowlarr βββ 11 indexers
β hands off to
qBittorrent
β ALL traffic via
Gluetun VPN βββΆ internet
β lands on
NAS βββΆ Jellyfin βββΆ TV
This is the project that started the whole homelab, honestly. I just wanted Plex without paying for Plex, and it kind of snowballed into a full automation pipeline.
The flow: Radarr and Sonarr go grab what I've asked for, Prowlarr feeds them indexers, and qBittorrent does the actual download β but only ever through the Gluetun VPN, so if the tunnel drops, nothing leaks. It lands on the NAS over NFS and Jellyfin serves it to the TV.
The part that took way too long was the VPN kill-switch and getting qBittorrent to talk to the *arrs through Gluetun's network β one wrong firewall port and the whole queue silently stalls. I've rebuilt that container combo more times than I'd like to admit.
I've since pulled two pieces back out. Tdarr was doing iGPU H.265 transcoding, but it sat resident 24/7 for a job that's really a batch task β so it came out and gets run on demand instead. Jellyseerr went the same way once I noticed I was requesting things directly in the *arrs anyway. Deciding what to remove turned out to be a bigger part of running this than deciding what to add.
03
π
Zero-Trust Access & Security
Vaultwarden (self-hosted Bitwarden) with Argon2id admin token and disabled registration. Nginx Proxy Manager handling all .lan domains with wildcard self-signed cert plus Let's Encrypt certs via Cloudflare DNS-01. Cloudflare Tunnels for public exposure with no open ports. Tailscale subnet routing for remote access. Fail2ban blocking brute-force attempts on exposed services.
VaultwardenNPMCloudflare TunnelTailscaleFail2ban
internet
β no ports forwarded. ever.
βΌ
Cloudflare Tunnel ββΆ NPM (TLS) ββΆ services
β
me, away ββΆ Tailscale ββΆ the LAN
secrets ββΆ Vaultwarden (Argon2id, signups off)
noise ββΆ Fail2ban ββΆ banned
The rule I set for myself: nothing on this network has a port forwarded on the router. Ever. If something's reachable from outside, it's because a Cloudflare Tunnel reached out to do it, or I'm on Tailscale.
Everything internal goes through Nginx Proxy Manager with TLS, passwords live in my own Vaultwarden (registration disabled, Argon2id on the admin token), and Fail2ban watches anything exposed and bans the obvious brute-force noise.
Being a security student, this is the part I actually care about getting right. It's a lot easier to reason about "zero open ports" than to constantly second-guess what's listening.
04
π‘
Home Automation Bridge
Eclipse Mosquitto MQTT broker enabling smart home devices (IR blasters, sensors) to communicate with Home Assistant. Auth-required with password file. Bridges a LinknLink eRemote to Home Assistant for IR control.
MosquittoMQTTHome AssistantIoTLinknLink
IR / RF signals
β
LinknLink eRemote
β publishes
Mosquitto (MQTT, auth required)
β subscribes
Home Assistant ββΆ automations fire
Smaller project, but a fun one. I've got a LinknLink eRemote that blasts IR, and I wanted Home Assistant to control the dumb IR stuff β the AC, some old gear β like it was smart.
Mosquitto sits in the middle as the MQTT broker, auth required, because an open broker on your network is basically a free microphone for anyone who finds it. The device publishes, Home Assistant subscribes, automations fire.
Nothing flashy, but it's the kind of glue that makes the whole "smart home" thing actually work instead of being five apps that refuse to talk to each other.
05
π
Monitoring & Observability
Uptime Kuma monitoring 19 services at 60-second intervals with Docker socket access for real-time container state. cAdvisor exporting container metrics to Prometheus on the monitoring VM. node-exporter providing host-level CPU, memory, and disk metrics. All of it feeds Grafana dashboards, and a Discord webhook yells at me when something actually breaks.
You can't fix what you can't see, so this layer exists so I'm not flying blind. Uptime Kuma pings 19 services every 60 seconds and yells at me the second one drops. cAdvisor and node-exporter ship container and host metrics over to Prometheus on the monitoring VM, and Grafana turns all of it into dashboards.
The honest reason it exists: I got tired of finding out something was down by trying to use it and getting an error. Now I usually know before anyone else in the house does.
06
βοΈ
Self-Hosted Knowledge Sync
Obsidian LiveSync on a self-hosted CouchDB keeps notes synced across every device in real time, replacing the $5/month Obsidian Sync subscription. A LiveSync Bridge container mirrors the whole vault back out as plain Markdown on disk, so scripts β and the automation on the server β can read and write the same notes my laptop sees.
phone ββ
laptop ββΌββΆ Obsidian LiveSync ββΆ CouchDB
desktop ββ real-time β
LiveSync Bridge
β
plain .md files on the server
(read/written by scripts + Delos)
stays on my hardware Β· goodbye $5/mo
I take a lot of notes β homelab stuff, CTF writeups, class β and I didn't love paying $5 a month for Obsidian Sync when I've got a perfectly good server sitting right there.
So I stood up CouchDB and pointed Obsidian LiveSync at it. My vault syncs across every device in real time, the data never leaves my hardware, and the subscription's gone.
The part that turned out to matter more than the money: a bridge container mirrors the vault into plain Markdown files on the server, both directions. That's what lets everything else write into it β my voice-note device, the nightly automation, the docs for this whole lab. A note written on the server shows up on my laptop seconds later, which means the server and I are genuinely taking notes in the same place.
07
ποΈ
NAS & Storage Infrastructure
Synology DS214 NAS with ~907GB usable storage mounted via NFS v3 to the Docker host. Hosts media, downloads, and backup storage. Proxmox backup target via a separate NFS export. fstab-mounted with _netdev and nofail for clean boot behavior.
Everything heavy lives on an old Synology DS214 β media, downloads, backups β mounted over NFS to the Docker host. About 907 GB usable, which fills up faster than you'd think once the *arrs get going.
The "took longer than it should have" award goes to the fstab mount options. Without _netdev and nofail, the VM would hang on boot waiting for a NAS that wasn't ready yet. Learned that one the hard way after a reboot left the whole thing stuck on a black screen. Proxmox backs up to a separate export on the same box.
08
π
Automation & Maintenance
Watchtower auto-updating all labeled containers nightly. Recyclarr syncing TRaSH Guides quality profiles to Radarr/Sonarr daily. Host OS auto-updates via unattended-upgrades with a Discord webhook notifying on success/failure, rebooting in the early morning if needed. Every scheduled job gets a timeout, a failure alert, and a watchdog that notices when it stops running at all.
Watchtower ββΆ pulls new images nightly
Recyclarr ββΆ syncs TRaSH quality profiles
unattended-upgrades ββΆ patches the host OS
cron guard ββΆ watches the jobs themselves
β on every run
Discord webhook ββΆ green / orange / red
after any reboot ββΆ boot check posts what came back
The goal here is to do as little manual maintenance as possible. Watchtower updates containers overnight, Recyclarr keeps my Radarr/Sonarr quality profiles in line with the TRaSH guides, and the host patches itself with unattended-upgrades and reboots in the early morning if it has to.
The piece I'm weirdly proud of is the Discord webhook β every update run pings me with green, orange, or red so I know what happened without logging in. It took a couple of tries to stop it from false-alarming on every single run, but now it just quietly works.
The lesson that cost me the most: a scheduled job that fails silently is worse than no job at all, because you trust it. So everything on a timer now has a timeout, an alert when it fails, and a separate watchdog that notices if it stops running entirely. After any reboot, a boot check posts to Discord confirming Docker, the NAS mount, the containers and DNS all actually came back.
09
π
DNS & Network Layer
AdGuard Home as the primary DNS server with ad blocking, custom .lan rewrite rules for all services, and DHCP-configured clients. All .lan domains resolve to the Docker host and route through NPM. DOCKER-USER iptables rules for container network security.
AdGuard HomeDNSiptablesNetworking
every device ββΆ AdGuard Home (DNS)
β blocks ads + trackers
β resolves *.lan names
βΌ
NPM ββΆ the right service
container traffic ββΆ DOCKER-USER iptables rules
AdGuard Home is the DNS for the whole house. It blocks ads and trackers network-wide and resolves all my internal names to the right place through the reverse proxy.
The catch with running the only DNS server on your network is that when it's unhappy, everything looks broken. I once spent an entire evening convinced AdGuard had died, when really the upstream resolver was timing out and a router reboot fixed it in thirty seconds. Lesson logged.
Container network rules are locked down through the DOCKER-USER iptables chain β never UFW, because UFW and Docker fight and Docker always wins.
10
π
Pentesting & CTF
Actively working through TryHackMe rooms and CTF competitions to practice offensive security. Hardware includes a Flipper Zero for wireless/RFID/IR research, a Pwnagotchi for wifi handshake capture, and a dual ESP32 setup for wireless experimentation. Daily drivers: Kali Linux, nmap, and gobuster.
Kali LinuxnmapgobusterTryHackMeCTFFlipper ZeroPwnagotchi
TryHackMe / CTFs ββΆ reps on the offensive side
βββββββββββββββββ the gear βββββββββββββββββ
β Kali Β· nmap Β· gobuster β
β Flipper Zero β RFID / IR / sub-GHz β
β Pwnagotchi β wifi handshakes β
β dual ESP32 β wireless tinkering β
βββββββββββββββββββββββββββββββββββββββββββββ
This is the "breaking things on purpose" half of the hobby, and the part that actually lines up with what I'm studying. I work through TryHackMe rooms and CTFs to get reps with the offensive side β recon, enumeration, the usual nmap-then-gobuster dance.
On the hardware side I've collected a Flipper Zero for RFID/IR/sub-GHz, a Pwnagotchi that quietly farms wifi handshakes, and a couple of ESP32s for wireless experiments. All of it on my own gear and my own networks β the whole point is understanding how the attacks actually work so I know how to defend against them.
11
π‘οΈ
SIEM & Security Monitoring
A dedicated VM running the full Wazuh stack (manager, indexer, and dashboard) for log ingestion and threat detection, with its own SSD for the indexer data. Prometheus collects metrics from every host via cAdvisor and node-exporter. Grafana ties it all together. Hands-on practice with the SIEM/EDR concepts I'm studying in school.
This is the project I'm most invested in, because SIEM and EDR are exactly what I'm learning in school β so I stood one up instead of just reading about it. A dedicated VM runs the full Wazuh stack: manager, indexer, and dashboard, ingesting logs and flagging suspicious behavior across everything on the network.
The live alert numbers on my dashboard page are pulled straight from this. Getting hands-on with real detections, tuning out the noise, and watching what actually trips the rules has taught me more than any slide deck. It's the closest thing to a real blue-team environment I can run at home.
12
π
This Website
Astro, server-rendered by Node in a container on the homelab and published through a Cloudflare Tunnel β no cloud host, no open ports, no monthly bill. The container count, CPU and RAM figures on the home page are pulled live from the Portainer and Proxmox APIs when you load it, and the live page keeps refreshing from Wazuh and Grafana after that.
AstroDockerCloudflare TunnelNode.js
visitor ββΆ Cloudflare Tunnel ββΆ this box
β
Astro (SSR / Node)
β pulls live
Portainer Β· Proxmox Β· Wazuh
no cloud host Β· no monthly bill
You're looking at it. This site is served straight off the homelab through a Cloudflare Tunnel β no cloud hosting, no monthly bill, just a container on the same box as everything else.
It's built with Astro and rendered on the fly by Node, which is why the container count and the live dashboard aren't screenshots β they're pulled from the Portainer and Proxmox APIs when the page loads, and the live page keeps refreshing itself after that.
Same as everywhere else here: Claude wrote the site, I decided what went on it and what wasn't good enough to keep. The words about me are mine.
The casino tab started as a joke and got completely out of hand, which is honestly the most accurate one-line summary of this entire hobby.