From 7957c621cf7a3648fe3a672ff79c3963dae1eb8b Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 14 Jul 2026 20:29:39 +0200 Subject: [PATCH] docs: how to run Lyra behind a Gluetun VPN container Adds a "Running behind a VPN (Gluetun)" README section: the whole-stack network_mode: service:gluetun setup (DATABASE_URL @db -> @localhost, ports moved to gluetun, FIREWALL_INPUT_PORTS + FIREWALL_OUTBOUND_SUBNETS), a compose sketch, and caveats (Qobuz geo-sensitivity, the IPv6 sysctl not applying but harmless, MB per-IP rate limit covered by the cache, bind mounts unaffected). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/README.md b/README.md index f34fed3..eb808a2 100644 --- a/README.md +++ b/README.md @@ -156,3 +156,89 @@ Lyra has **no per-user accounts**. Two layers protect it: cookie, plain HTTP on the LAN). For anything internet-facing, also put Lyra behind a reverse proxy with TLS and/or a VPN or auth gateway — don't rely on the shared password alone. + +## Running behind a VPN (Gluetun) + +Lyra runs fine with its traffic routed through a [Gluetun](https://github.com/qdm12/gluetun) +VPN container — useful for hiding the download traffic (Soulseek / Qobuz / YouTube). Nothing +in Lyra is VPN-incompatible; the only thing that changes is container networking, because a +service using `network_mode: "service:gluetun"` gives up its own network stack and shares +Gluetun's. Two consequences: + +- Containers that share Gluetun's namespace reach each other over **`localhost`**, not by + Docker service name — so `DATABASE_URL` changes from `@db:5432` to **`@localhost:5432`**. +- **Published ports move to the `gluetun` container** (the individual services can no longer + publish their own). + +The simplest, most consistent setup is to put the **whole stack** behind Gluetun. Sketch +(merge into your existing Gluetun compose; adjust the VPN provider block to yours): + +```yaml +services: + gluetun: + image: qmcgaw/gluetun + cap_add: [NET_ADMIN] + environment: + # ... your VPN provider / credentials ... + FIREWALL_INPUT_PORTS: "3000" # let your browser reach the web UI + FIREWALL_OUTBOUND_SUBNETS: "192.168.0.0/16" # your LAN — so the worker can reach slskd + ports: + - "8770:3000" # Lyra web (published on gluetun, not on `web`) + # - "5432:5432" # only if you want DB access from the LAN + + web: + build: ./web + network_mode: "service:gluetun" # no `ports:` / `networks:` here + environment: + DATABASE_URL: postgresql://lyra:lyra@localhost:5432/lyra # `db` -> `localhost` + LYRA_SECRET_KEY: ${LYRA_SECRET_KEY} + LYRA_PASSWORD: ${LYRA_PASSWORD:-} + HOSTNAME: 0.0.0.0 + LYRA_LIBRARY_ROOT: /music + volumes: + - ${MUSIC_DIR:-./music}:/music + depends_on: + gluetun: { condition: service_healthy } + db: { condition: service_healthy } + + worker: + build: ./worker + network_mode: "service:gluetun" + environment: + DATABASE_URL: postgresql://lyra:lyra@localhost:5432/lyra + LYRA_SECRET_KEY: ${LYRA_SECRET_KEY} + STAGING_ROOT: /staging + SLSKD_DOWNLOADS_ROOT: /slskd-downloads + volumes: + - ${MUSIC_DIR:-./music}:/music + - ${STAGING_DIR:-${MUSIC_DIR:-./music}/.staging}:/staging + - ${SLSKD_DOWNLOADS_DIR:-./slskd-downloads}:/slskd-downloads + depends_on: + gluetun: { condition: service_healthy } + db: { condition: service_healthy } + + db: + image: postgres:17 + network_mode: "service:gluetun" # shares the namespace so `localhost:5432` resolves + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - postgres-data:/var/lib/postgresql/data +``` + +(Give `db-backup` the same `network_mode` and `PGHOST: localhost`.) The `HOSTNAME=0.0.0.0` +and the built-in healthchecks keep working, since everything is in one namespace. + +Caveats worth knowing: + +- **Qobuz is geo-sensitive** — its catalog/availability varies by country, so pick a VPN + exit in a region where your Qobuz account works, or some downloads will fail to match. +- The worker's `net.ipv6.conf.all.disable_ipv6` sysctl **can't apply behind Gluetun** (a + shared netns can't set sysctls) — harmless, because Gluetun blocks IPv6 anyway, which is + exactly what that sysctl worked around (Qobuz's CDN resolving to an unroutable IPv6). +- MusicBrainz rate-limits per IP; a shared VPN exit is marginally more likely to hit the + 1 req/s ceiling, but the built-in response cache makes that a non-issue in normal use. +- Bind mounts (`/music`, `/staging`, `/slskd-downloads`), the Postgres volume, and backups + are filesystem, not network — **completely unaffected** by the VPN.