# Jellyfin Server — Docker Services ## Host - **VM / alias**: jellyfin - **IP**: `192.168.88.10` - **SSH**: `jgitta@192.168.88.10` (alias: `jellyfin`) - **Portainer endpoint ID**: 4 - **Compose files**: managed via Portainer (stack IDs below); source of truth is Gitea `jgitta/homelab-configs` --- ## Stacks | Stack | Portainer ID | Gitea Path | |---|---|---| | arr-stack | 35 | `jellyfin/arr-stack/docker-compose.yml` | | portainer-agent | 36 | `jellyfin/portainer-agent/` | | node-exporter | 44 | `jellyfin/node-exporter/` | --- ## arr-stack Services | Container | Image | Port | Config Path | |---|---|---|---| | radarr | lscr.io/linuxserver/radarr:latest | 7878 | /srv/docker/radarr/config | | sonarr | lscr.io/linuxserver/sonarr:latest | 8989 | /srv/docker/sonarr/config | | prowlarr | lscr.io/linuxserver/prowlarr:latest | 9696 | /srv/docker/prowlarr/config | | flaresolverr | ghcr.io/flaresolverr/flaresolverr:latest | 8191 | — | | watchtower | containrrr/watchtower | — | see below | | watchtower-prowlarr | containrrr/watchtower | — | see below | Media volume: `/mnt/media` on jellyfin host → mounted as `/data` in radarr/sonarr. --- ## Watchtower — Split-Instance Setup Two Watchtower instances run in the arr-stack to stagger registry checks and avoid rate limiting lscr.io/ghcr.io with too many concurrent requests. | Instance | Schedule | Scope | Manages | |---|---|---|---| | `watchtower` | 4:00 AM | (none) | radarr, sonarr, flaresolverr, portainer-agent | | `watchtower-prowlarr` | 4:05 AM | `prowlarr` | prowlarr only | The scope works via Docker labels: prowlarr has `com.centurylinklabs.watchtower.scope=prowlarr` — this tells the main `watchtower` to skip it, and tells `watchtower-prowlarr` to manage it. Both instances mount `/home/jgitta/.docker/config.json:/config.json:ro` for authenticated registry pulls. --- ## Registry Authentication (fixed 2026-05-20) **Problem**: Watchtower was hitting lscr.io/ghcr.io rate limits and timeouts with unauthenticated requests. lscr.io is a proxy that redirects to ghcr.io for the actual registry API — credentials for lscr.io alone were not enough; ghcr.io also needed auth. Two separate issues were compounding: 1. Unauthenticated requests shared a small anonymous quota 2. Two concurrent Watchtower instances were doubling all requests at 4 AM **Fix**: Three sets of credentials were configured — both as the `jgitta` user (for Watchtower's own registry digest checks) and as `root` (for the Docker daemon's actual image pulls): ```bash # As jgitta (Watchtower reads /home/jgitta/.docker/config.json via volume mount) echo | docker login lscr.io -u jgitta --password-stdin echo | docker login ghcr.io -u jgitta --password-stdin # As root (Docker daemon uses /root/.docker/config.json for docker pull) echo | sudo docker login lscr.io -u jgitta --password-stdin echo | sudo docker login ghcr.io -u jgitta --password-stdin ``` Token used: the no-expiry `read:packages` GitHub PAT in credentials.md. **If token ever needs rotating**: run the four `docker login` commands above on jellyfin with the new token. No compose changes needed — both config files are read live. --- ## Notes - Do NOT run `docker restart watchtower` manually while `watchtower-prowlarr` is running — Watchtower's multi-instance detection will cause one to kill the other. Use `docker restart watchtower watchtower-prowlarr` to restart both together, or let Portainer manage redeployment. - Portainer stack 35 is the authoritative runtime config. If it drifts from Gitea, update via Portainer API with `prune: true` to remove orphaned services.