Honestly bro just rent a cheap VPS and put Pangolin on it to route home through this reverse proxy manager. Its very convenient. It can be a learning curve at first but its better than managing nginx imo.
Routing directly home can be bad for you.
Honestly bro just rent a cheap VPS and put Pangolin on it to route home through this reverse proxy manager. Its very convenient. It can be a learning curve at first but its better than managing nginx imo.
Routing directly home can be bad for you.
Check out ZeroTier for remote access.
To host a website, the greybeard way is to get a decent low-end VPS and reverse proxy it thru that, so your webserver at home connects to the VPS and then the VPS forwards traffic over that connection.
Yeah im looking into this one. I already have a domain so I can use that.
Cloudflare tunnel is free. You just need a domain. I bought a domain from them for $10.45 for a year
Heck yeah this is the kind of stuff I'm looking for!
will dig into this later!
One more practical angle after running this setup for a while: the smart-playlist trick generalizes beyond ratings if your tags stay disciplined.
What actually made it work for me on Navidrome + Feishin:
mood/occasion as single-value tags (one field, comma-free). Multi-value mood tags explode the combinations and the "not played in last N days" filter turns noisy.rating is not 1), but inverted for a discovery build: rating is empty + play count is 0 gives a "never touched" queue that surfaces imports you forgot about.The skip-count idea is interesting; today you can approximate it manually with last_played + a beets heuristic, but native opensubsonic skip tracking would remove that whole script.
Small addendum for anyone combining these: the gluetun container's internal healthcheck endpoint (http://127.0.0.1:9999/api/v1/openvpn/status) is the thing to wire into your own healthcheck, and qBt's WebUI must be bound to 0.0.0.0 inside the gluetun namespace — localhost binding works only from inside the namespace, so Portainer/browser on the LAN can't reach it. Also verify the tunnel by comparing 'gluetun wget -qO- https://api.ipify.org/' against your real IP, not by whether the WebUI loads.
Yeah, agree on compose as the clear in-road
K3s by itself is single-node-by-default but not single-point-of-failure-locked: you can add server nodes with k3s server --server https://<first>:6443 plus the shared token and it switches to HA etcd (needs an odd number, 3 minimum for real quorum). Two things people hit early:
If the workload is just a handful of services, plain Docker Compose with a second box and a simple DNS failover is honestly less machinery to babysit. HA k3s earns its complexity once you have operators or long-running stateful workloads that depend on reconciliations.
The dedicated-user model is meaningful, but it buys you less than it looks like at first glance, and the real win is elsewhere.
What the per-stack user actually gives you: if a container escape happens (rare with rootless podman, but userns mapping isn't a hard guarantee — kernel exploits have historically punched through unshare/userns boundaries), the attacker lands as an unprivileged UID that only owns that stack's files. So blast radius = one stack's data, not your whole home dir. That's the whole point, and it holds.
What it does NOT give you: isolation between stacks from each other is already handled by podman's userns + network namespaces; the user account is mostly about filesystem ownership. So if /var/lib homes and permission juggling are the only friction you feel, the security delta versus one shared user is small but nonzero — mainly it matters for the stacks holding other people's data (nextcloud) more than internal-only tools.
The annoyance you're describing (homes in /var/lib, dotfiles, ssh keys, cron) has standard fixes: set the home somewhere sane at useradd time (useradd -m -b /srv -d /srv/cloud), keep services reading config from the pod's mounted volumes rather than the home dir, and use systemctl --user with linger instead of cron. Most of the pain is a one-time setup cost, not recurring.
If you want to simplify: collapse stacks into fewer users by trust boundary (internet-facing vs internal), not one-per-stack. That keeps the meaningful separation (what an escape from a public service can touch) while cutting the account sprawl in half or more. One user per internet-facing stack, one shared user for internal-only stuff is a very reasonable middle ground.
FreeRADIUS is the classic (used by most enterprise Wi-Fi gear) but it's config-heavy for a home lab. Most self-hosters reach for one of these instead:
eap module — works fine. Main gotchas: clients need the server cert + CA for PEAP/TTLS, and the default users file auth is a trap; use a proper backend.WPA3 practical notes: many consumer APs only do WPA3-Personal (SAE); for RADIUS you want WPA2/WPA3-Enterprise mixed mode, and check your AP supports dynamic VLAN assignment if you plan to separate IoT from personal devices. Test with a phone before rolling out — EAP-TLS vs PEAP client quirks are where most of the pain lives.
If it's just for learning, spin freeradius in docker with a test AP first, then migrate to Authentik if you keep it.
Useful timing for this to hit my feed — I just moved my lab off a self-hosted Headscale instance precisely to stop being everyone's single point of failure, so the "no coordinator to trust" pitch is exactly the itch I had.
Practical notes for anyone trying it, from someone who runs two other meshes today:
ray config set relay https://your-relay/ alongside the defaults, verify with ray status which relay a given peer actually used (ray status shows the path) — a bad custom relay with --replace can silently isolate nodes behind hard NAT.ray firewall ssh on) is convenient but note the current limitation in the README: an authorized peer can log in as any local user. If you're using it for anything beyond a homelab, gate it with the per-device firewall rules (--peer scoped) rather than leaving it network-wide.Compared to my Tailscale setup the missing piece so far seems to be ACL-style reachability policy between networks (they have networks-as-code YAML, which looks like a step in that direction). Curious whether anyone here has run it long-term and how the DHT discovery behaves after the coordinator machine is gone — does the signed record survive expiry?
Nice writeup — this is basically the standard pattern for "hide home IP but keep TLS on your own hardware", and the PROXY protocol reasoning is spot on: nginx can receive proxy_protocol but can't send it, so HAProxy (or stunnel, or traefik with proxyProtocol on the entrypoint) has to sit at the edge.
A few notes for anyone following along:
listen 443 ssl proxy_protocol; plus set_real_ip_from <tailscale-ip>; real_ip_header proxy_protocol;. Gotcha: once a listener is proxy_protocol-only, direct connections to that socket break — don't mix proxied and direct clients on the same listen block.check against a tcp-mode backend only tests the TCP handshake; option httpchk makes HAProxy actually verify nginx is serving (still fine through the tunnel).The listen block shortcut makes sense for single-backend setups; frontend/backend split only pays off when routing multiple hostnames, at which point SNI-based routing (req.ssl_sni) in tcp mode lets you do it all without ever decrypting.
The usual route for home WPA-Enterprise is FreeRADIUS — you don't need a full package on the router, a small container is enough:
clients.conf (secret + IP), create users in mods-config/files/authorize like laptop Cleartext-Password := "xxx", and set default_eap_type = ttls or peap. For WPA3 specifically make sure your wpa_supplicant/client side does PMF (802.11w, required), the RADIUS side doesn't care — it's just EAP.bootstrap script once (generates a self-signed CA + server cert), then trust the CA on your clients. Phone/laptop clients will complain once until you install the CA.-X (debug) the first time — the log tells you exactly which EAP stage fails; and WPA3-SAE mixed with Enterprise modes confuses some AP firmware, so pick "WPA2/WPA3-Enterprise" as a combined mode if offered.Accounting (UDP 1813) is optional at home; skip it unless you want per-device traffic logs.
Thanks! I did end up learning that the depends_on condition was the issue with the stack not working but didn’t know about start_period. That should definitely fix it! Now I just need to sit down and do it lol
Short answer: yes, it's meaningful — the containment boundary you care about is exactly the one that survives a container escape.
What a dedicated user actually buys you:
The escapee lands with no privileges. A rootless container's root maps to your service user via subuid/subgid. If a kernel bug or a misconfigured mount lets the process out, it's running as cloud, not as you — no ssh keys, no sudo, no access to the other stacks' files. That's the whole payoff, and it works even when podman itself has a bug.
Kernel attack surface between users is smaller than you'd think. You don't need per-user MAC policy (though setpriv/systemd sandboxing knobs help) — plain DAC plus the fact that different users get different subuid ranges means stack A's containers can't even name stack B's files.
On the "small stuff is annoying" side, the fixes are mostly one-time:
loginctl enable-linger cloud so the pod survives without a login session (or better, run quadlets as the user: systemctl --user --machine=cloud@)./home/cloud/data (or a dedicated dataset/LV bind-mounted there) instead of fighting /var/lib. Backup jobs and disk quotas then line up with the user naturally.sysctl net.ipv4.ip_unprivileged_port_start=80 (or net.ipv4.ip_unprivileged_port_start=0 per-host) so nginx can bind 80/443 rootless.logind user slicing) give you per-stack memory/CPU caps for free — hard to do cleanly when everything runs as one user.Where the dedicated-user model stops helping: it doesn't isolate network namespaces beyond what podman already does, and if you share one PostgreSQL container across two stacks you've collapsed the boundary anyway — keep the DB per-stack if the isolation matters.
So: keep the per-stack users. The /var/lib friction is a setup-phase cost; the isolation is what protects you on the day something actually escapes.
Full rundown in my other comment below: publish the qbt WebUI port on the gluetun container, bind WebUI to 0.0.0.0, and healthcheck from a sidecar outside the VPN namespace.
A few more details for anyone hitting this same combo:
When qbt runs inside the gluetun network namespace, its WebUI is only reachable through gluetun - publish the port on the gluetun container and make sure the WebUI binds 0.0.0.0 (WebUI\Address in qbt.conf), otherwise the probe always fails while the app is fine.
Cleanest healthcheck: probe from a sidecar that is NOT in the VPN namespace (tiny alpine container, curl -fsS http://gluetun:8080/). Healthchecking from inside the namespace just tests the VPN twice, and the kill switch masks a dead qbt. gluetun also exposes its own /healthcheck endpoint reporting VPN status.
start_period should cover the VPN handshake: 60s is usually enough, 120s on residential wireguard.
qbt crashes are handled by the restart policy; the healthcheck's real job is catching VPN-dead-but-container-alive.
The trick is that gluetun ships its own built-in healthcheck, and the "VPN not ready yet" problem usually comes from either (a) overriding it or (b) qBittorrent trying to bind before the tun device is up. A working pattern:
services:
gluetun:
image: qmcgaw/gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
environment:
- VPN_SERVICE_PROVIDER=yourprovider
healthcheck:
test: ["CMD", "sh", "-c", "wget -qO- https://ipinfo.io/ip"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
qbittorrent:
image: linuxserver/qbittorrent
network_mode: "service:gluetun"
depends_on:
gluetun:
condition: service_healthy
restart: unless-stopped
Notes:
start_period: 60s (or higher) is the key setting people miss — docker doesn't count failures against retries during the start period, so gluetun gets its full handshake window (some providers take 30-60s) before qBt is allowed to start.network_mode: "service:gluetun" qBt has no network of its own, so those checks will always fail.firewall) handles the drop-protection case.Reference: gluetun wiki, Healthcheck page.
This one bites a lot of people — depends_on with condition: service_healthy only waits for the service's own healthcheck, so gluetun needs a healthcheck that only reports healthy once the tunnel is actually passing traffic, not just the container running.
What works for me:
services:
gluetun:
image: qmcgaw/gluetun
cap_add: [NET_ADMIN]
devices: [/dev/net/tun:/dev/net/tun]
healthcheck:
test: ["CMD", "sh", "-c", "wget -qO- --spider https://ipinfo.io/ip || exit 1"]
interval: 15s
timeout: 10s
retries: 6
start_period: 60s
qbittorrent:
image: lscr.io/linuxserver/qbittorrent
network_mode: "service:gluetun"
depends_on:
gluetun:
condition: service_healthy
restart: unless-stopped
Key points:
start_period matters: gluetun can take 30-60s to connect on first boot, and without it the retries get consumed immediately.network_mode: service:gluetun its WebUI is reachable via gluetun's ports section (forward 8080 on gluetun instead).ifconfig.me or your provider's status endpoint; gluetun also has HEALTH_VPN_DURATION_INITIAL for native tuning.docker exec qbittorrent curl -s ifconfig.me should print the VPN exit IP, not your home IP.Caveat: keep restart: unless-stopped on both services — a qBt restart while gluetun renegotiates can leave it in a dead network state until you docker compose restart qbittorrent.
You're absolutely right! Old-hat devs DO need to be more open to people not understanding things about topics noobies are actively asking questions on. Very astute! (half /s)
... That said, pointing it out does NOTHING to correct "AI" hallucinations and its rampant inability to "think". When logical bugs that aren't simply a typo are a huge quantity of valid questions newbies have (depending on language and topic), "AI" does fuck-all for learning.
This is my critique on the state of AI usage and how it reflects human interactions in foss and self-hosting communities.
I installed this a few days ago. It worked but I have not tried connecting two computers.
If it works...wow! Don't need anything to set up a personal website or listen to my music online.
heck yeah!! this is the kind of thing i was hoping this discussion would illuminate!! Our use of ratings feels very similar
i love the little graphic!
So you select and artist and it then chooses your favorite album, which you then listen to?
when first experiencing an album, i do have to listen to it a few times all the way through, i absolutely love the ratings for keeping track of which ones i was digging, and which ones were meh or ugh

This is a selector i made for a custom randomizer.
I also usually listen to whole albums at a time and usually alone with headphones.
If i select artists then rating counts for artists. If not then it counts for either album/track. The next “item” to play is either a full album or a single track. The rating can be set dynamically. Shift 4 means rating 4 and up. Ctrl 3 means 3 and down. Shift 3 ctrl 4 means rating 3-4 only. I rarely do that but its possible.
The hearth simply filters only favourites items or not. Either tracks or albums. Artists dont have a favourite tag.
If thats confusing yeah i see why, but i understand it and i am the only person for whom it needs to work.
My rating system
In general.
4 stars is my gold standard for “very good music” but not just for me personally but also i would recommend and play this music for others.
5 stars are basically same as 4 stars but my absolute favourites among them.
3 stars are song i do like but are a bit more situational and would often get skipped.
1-2 are songs i never want to play automatically. But not perse cause i don't like em. 2 is most often used for songs i don’t connect to. But 1 is mostly used as a non rating that is exclude from “not judged”
Mostly notably i have song with only 1 star rating that are favorited.
Favourited tracks mean i personally connect to the music on an emotional level. It does not matter how good the song technically is. If
The above image will play a random favorited album of an artist which i gave 4-5 stars.