ENTRY

[ESC]
25d2960 words3 saves1 reply

Homelab

Knowledge Base

Let me begin by stating that I am self-taught. There are things I may have wrong, so please don’t take anything I say as gospel. My formal education has nothing to do with technology — throughout my undergrad and postgrad, I never had to write a single line of code or touch a terminal. You have been warned.

That said, I’d love to hear your thoughts, comments, and suggestions. Please share your own setup, or what you’re planning to build.

Hardware

I have a slightly complicated setup for a homelab — I run two servers. One is dedicated to storage, the other handles all my applications.

TrueNAS Scale (NAS)

My storage server is a UGREEN 2-bay “toaster” NAS running TrueNAS Scale (Community Edition). It’s powered by an Intel N100 chip with 32 GB of DDR5.

It currently has two 28 TB HDDs in a ZFS stripe — yes, no parity, I like to live dangerously. 28 TB is the advertised size; the practical formatted size is closer to 25 TB per drive, and ZFS takes another ~20% cut for overhead. After all that, I have roughly 45 TB of usable space. This server is strictly for storage.

On the power-saving side, I have HDD Standby set to 60 minutes and Advanced Power Management at 127 so the drives can actually park when they’re idle. The system dataset lives off the spinning pool so background ZFS noise doesn’t keep them awake.

Alma Linux (App Server)

All my applications run on a mini PC running Alma Linux. I originally started on CentOS and migrated over when CentOS Stream stopped making sense for a stable home server.

It’s powered by a Ryzen 9 8945HS with 32 GB of RAM and a 1 TB NVMe drive. The Radeon 780M iGPU is what handles transcodes and (currently) my local LLM experiments through ROCm. In the UEFI, I’ve allocated up to 16 GB of RAM to the iGPU as VRAM, plus 8 GB of swap as overflow.

The Alma Linux server talks to the NAS over NFS — the TrueNAS share is mounted locally so all my containers see the media library as if it were a local folder.

Container Runtime

I’ve used both Docker and Podman (and Apple Container now too!), but I’m currently on Podman. While both can be configured to be nearly identical in function and security, I picked Podman for two reasons: my day job might be incorporating some RHEL servers and I want to get comfortable with that stack, and from what I understand, Podman is a little more secure and hardened than Docker out of the box.

One of those security features is that Podman containers can be run rootful or rootless. Running rootless means that if your application gets compromised, it’s much harder for a bad actor to tamper with your host — they’re essentially trapped inside the container without root access to the system.

Another advantage is that Podman has no long-running root daemon. Docker traditionally runs dockerd as root, which is a privileged process the Docker client talks to. A compromise of that daemon historically meant a compromise of the host. (Docker now supports rootless mode too, but it’s opt-in and less common in practice.)

Containers and Pods

Some gross oversimplification: a pod is a group of containers running in the same logical “box,” sharing a network namespace. It’s similar in spirit to how stacks work in Docker. There are real technical differences between the two, but they don’t really matter for this write-up.

I currently have two pods that run rootful. They’re only rootful because they absolutely have to be — there are workarounds to run them rootless, but they add significant complexity and tend to break more easily. Everything else runs rootless.

download_pod (rootful)

My download pod is Gluetun, SABnzbd, and my Arr stack. Containers inside:

  • Gluetun — WireGuard tunnel via ProtonVPN
  • SABnzbd — Usenet downloader with strict SSL
  • Lidarr — music
  • Radarr — movies
  • Sonarr — TV and anime
  • Prowlarr — indexer manager (NZBgeek + NZBPlanet)

I started out with NordVPN, but Nord added a lot of unnecessary complications and the connection would drop periodically, dragging the whole Arr stack down with it. Nord would essentially log me out of my own VPN session and I never figured out why. I fought with it for about three months before giving up and switching to ProtonVPN, and I haven’t had a single issue since.

I also run WireGuard instead of OpenVPN inside Gluetun. The short version is that OpenVPN can cut your download speeds in half or more.

SABnzbd is set to strict SSL. Even though my traffic is already encrypted from my ISP, I wanted the extra layer of running everything through a VPN. Downloads land in a specific folder on the Alma Linux server (NVMe-fast); from there, Lidarr, Radarr, and Sonarr inspect and rename the files, then move them onto TrueNAS where the media library lives.

I originally had files downloading straight to the NAS, but that cut speeds in half and caused a lot of failed downloads. The Arr services are usually good about cleaning up bad files, but not always. To keep the staging area healthy, I have a cron job that wipes SABnzbd’s complete/incomplete folders whenever free space on Alma drops below 200 GB. It’s brutish and lazy, but it works — zero issues so far.

Prowlarr plus a good Usenet indexer is the glue that makes the whole thing work seamlessly. I bought the lifetime memberships to both NZBgeek and NZBPlanet. Honestly, one would have been enough — there’s about a 99% overlap — but they’re cheap and once in a while I get a hit from one that the other misses. If I had to recommend just one, I’d go NZBgeek because I have slightly better luck with audiobooks and ebooks there.

Even so, audiobooks and ebooks are notoriously hard to automate. That’s not the Arr tools’ fault — it’s an indexer problem. I’ve had more success doing manual searches and feeding the results to SABnzbd than I have with any dedicated automation.

I’ve also tried qBittorrent with Jackett and didn’t love it. The honest truth is that Usenet is the way to go for me, and torrenting is a decent backup if you’re willing to hunt down communities with healthy seeders. I’m not trying to talk anyone out of torrents — for my use case, Usenet is just good enough that I don’t need them.

jellyfinpod (rootless)

My jellyfinpod contains:

  • Jellyfin
  • Seerr (formerly Jellyseerr)
  • Wizarr

Jellyfin is where my friends and family goes to watch my legally obtained public domain media that exists… somewhere. I own a few domain names and one of them points at Jellyfin, so people can navigate there, log in with their own credentials, and watch whatever’s downloaded. I have three categories: Anime, Movies, and TV Shows. All the actual media files live on the NAS. When someone hits play, the Alma server reaches out to TrueNAS to pull the file.

All metadata and thumbnails are stored locally on the Alma server, not on TrueNAS. That way, when people are browsing, the thumbnails load at NVMe speed instead of HDD speed.

If someone wants something that isn’t on the server, they can hop over to Seerr. Seerr is connected to Jellyfin via API keys, so it can see what’s already downloaded, what’s been requested, and what’s queued. I can also edit and delete entries inside Seerr and it’ll tell Sonarr and Radarr to clean up. Users sign into Seerr with their Jellyfin credentials, which keeps onboarding stupid simple. I have it set to unlimited requests right now because I have so much space. Back when I had ~7 TB of usable storage, I had TV shows turned off and a 10-movie-per-month request limit per user.

This is probably the right place to talk about file sizes. I originally capped movies at 2 GB and that was great — it captured about 90% of what’s out there. Now that I have so much space, I’ve bumped the max to 5 GB. I also have Sonarr and Radarr set to upgrade until they find a WEB-DL 1080p file. That’s probably the lowest of the 1080p tiers, but for practical purposes it’s a transparent lossy format, especially compared to what you actually get from Netflix, Prime Video, Disney+, and the rest.

I deliberately don’t set min/max sizes for Sonarr. TV shows and anime are packaged in too many different ways and size limits make things hard to grab. Instead, I just set it to prefer and upgrade to WEB-DL.

As of this writing, I have 3,100+ movies, 50-something TV shows, and 7 anime series totaling around 19 TB. Several of those TV series are 100+ episodes. WEB-DL is a pretty good sweet spot if you want a large library without throwing unlimited money at drives.

Wizarr is a great little tool. I usually keep this container shut down (more on that in a second), but when I need it I spin it up and use it. Wizarr lets me generate an invite link I can send to friends and family. They click it, create their own Jellyfin username and password, and Wizarr walks them through downloading the Jellyfin app, setting it up on their phone or TV, and making requests through Seerr. It takes a bit to set up the first time, but you just reuse the template forever after that. I onboarded 30+ friends and family in two days instead of doing it by hand.

The reason it stays off is a small quirk where it will occasionally delete Jellyfin user accounts. I haven’t investigated deeply and I believe there’s a fix, but it’s not a service I need running 24/7. I try to keep my attack surface as small as possible, so when a service isn’t actively needed, I shut down both the container and its public domain. I still recommend Wizarr — it saved me a ton of work.

musicpod (rootless)

This pod contains Navidrome, AudioMuse-AI, Postgres, and Redis. The pod lets me privately stream my own music library. On my phone I access it through the Arpeggi app. Same pattern as Jellyfin: thumbnails on Alma’s NVMe, audio files on TrueNAS.

There’s no “request” feature here. To add new music, I use Lidarr. Recent Lidarr updates have made plain artist searches more reliable, but you can also use the lidarr: <MusicBrainz ID> search format for exact matches.

Everything is saved as FLAC. Eventually I want to run a script that converts all the FLACs to a transparent lossy format for portable devices. I modded an old iPod 5.5 to play my library and discovered that having 5K songs in FLAC absolutely breaks the poor thing — a smaller format would probably fix it. But that’s a post for another day.

plexpod (rootless)

This pod runs my Plex instance. It’s fully set up, but I don’t really use Plex for much anymore. I originally kept it around for Plexamp, which is genuinely the best audio streaming app I’ve used, and I might switch back. I don’t have any movies attached to this Plex right now, although back when I did it shared thumbnails with Jellyfin. Currently it only sees my music library, and there’s no port open to the router.

freshrss (rootless)

FreshRSS aggregates my news feeds. I’m trying to peel myself off Big Tech’s algorithm as much as possible, so I built my own feed. Retention is set to 100 days, which I think is the default; after that, articles roll off automatically.

couchdb (rootless)

I’m a heavy note-taker. I used to live in Notion, but I wanted to start actually owning my data and I like the local-first idea, so I switched to Obsidian. I also take notes across a bunch of devices, so I self-host a CouchDB instance as the LiveSync backend. It’s currently keeping four devices in sync. The container is bound to localhost only and managed by a user-mode systemd service so it comes up automatically with the rest of the stack.

cloudflared (rootless)

The way I expose services to the internet is through Cloudflare Tunnel. I let Cloudflare handle the heavy lifting — no open ports on my router, no inbound holes. Technically you’re not supposed to use it for streaming, but for a self-hosted service with only a handful of people on it at a time, it’s never been flagged for me. Still: just be aware that streaming over Cloudflare Tunnel is outside their intended use.

firefox (rootless)

I run a Firefox container purely so I can reach things on my local network from outside. The Firefox container itself is not exposed to the internet through Cloudflare; I access it through Tailscale.

Tailscale (host)

I run Tailscale on the host itself, not in a container. Any service I don’t want exposed to the internet but still need occasional remote access to gets reached through Tailscale. For example, if I need to check Lidarr, I just navigate to tailscale-IP:port and manage it like I’m on the LAN. It’s convenient and secure.

I also run Tailscale in a container on TrueNAS Scale. That’s the only container I run on the NAS — I keep everything else off it.

llmpod (rootful)

This one has to be rootful for GPU device passthrough (although Jellyfin Pod has GPU passthrough and it’s rootless), and I’ll be removing it soon. Inside the pod I have Ollama, Postgres, LiteLLM, and a Hermes Agent. Hermes was originally pointed at a Gemma 4 - class MoE model, but the iGPU makes token generation painfully slow. Even with up to 24 GB of RAM-as-VRAM and swap as overflow, it’s slow and mostly unusable. I have used a much smaller Gemma 4 model but it’s only god for a few parlor tricks over Telegram. I’ll most likely retire this pod.

A Few Other Services Worth Mentioning

A handful of services don’t fit neatly into the pod story but are part of the actual setup:

  • Audiobookshelf — audiobook and podcast server, reading from the NAS read-only.
  • Filebrowser — a quick web-based file manager so I don’t always have to SSH in to move things around.

I’ve also run RomM in the past, which organizes a video game ROM library and even streams games to your device like a tiny GeForce Now or OnLive, using your server to do the heavy lifting. My setup isn’t the beefiest for gaming, but I was able to stream Tekken 3 well enough, which I thought was pretty cool.

Security

One of the things I like about Alma Linux is that it’s security-focused. The system is locked down by default and pushes you to learn about security along the way. I originally started on Ubuntu, which is a much easier launchpad to learn on but very permissive out of the box.

A few specifics:

  • IPv6 is disabled on both servers, and every Podman container also has IPv6 explicitly disabled via --sysctl net.ipv6.conf.all.disable_ipv6=1.
  • All ports are closed by default on Alma. Every time I spin up a new container, I have to deliberately open its port; when I delete a container, I close the port back up.
  • Containers run rootless wherever possible; rootful is the exception, not the rule.
  • Internet exposure is only through Cloudflare Tunnel — no open inbound ports on the router.
  • Anything I need to reach remotely that isn’t public goes through Tailscale.

I recommend reading through the RHEL documentation. I know that sounds boring, but it’s genuinely useful. When I started on Ubuntu Server LTS, everything Just Worked — because nothing was locked down. RHEL (and Alma by extension) makes you deliberately poke holes where you need them. That creates a bit of setup overhead, but the result is a generally more secure system.

Tooling and Philosophy

I don’t use any container management platforms (Portainer, Docker Desktop, Podman Desktop, etc.). I do everything from the CLI. I usually SSH in from a Mac or Linux machine.

I find the GUI-based container builders pretty confusing. I started there, got lost quickly, then discovered docker-compose and life got easier. From there I leaned on AI tooling for a while, and then I realized I could just write the YAML and run the commands directly. What I’ve found, for me, is that container management platforms add an unnecessary layer of complexity. You can do everything you need from the CLI with ease, and you don’t have to learn a GUI that will eventually get rewritten. If you’re currently using Portainer or something similar, I cannot recommend enough making your way over to the CLI.

Closing Thoughts

I’ve upgraded and rebuilt these servers a handful of times now. Lately I’ve been trying to leave things alone because I want to give my Jellyfin users some stability. I find the current state of having to rent everything we use to be a bit appalling (Mongo is appalled too). So I want to build something that helps friends and family cut down on subscriptions as much as possible — strictly with legal public domain stuff, of course. No piracy here. Piracy bad.

I’ve spent so much time writing this, I’m not sure if I’ve covered everything. If you have questions, concerns, or recommendations, please leave a comment. I’m not an expert by any means — I’m an amateur who loves playing with tech. There’s still a lot for me to learn and I don’t doubt I’m doing some things wrong.

I’ll leave you with a quote I heard from YouTuber Jeff Geerling, who I believe was quoting someone else:

“If buying is not owning, then piracy is not stealing.”

1 reply

Log in to read the replies and join the conversation