# 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 --- ## Stacks (Portainer) | Stack | 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 | — | Media volume: `/mnt/media` → mounted as `/data` in radarr/sonarr. --- ## Auto-Updates — Cron + Watchtower run-once Watchtower is NOT run as a persistent container. Instead, cron jobs on the jellyfin host invoke Watchtower in `--run-once` mode, one container at a time, at staggered 3-minute intervals. This serializes all lscr.io registry checks and eliminates the CDN burst rate limit errors that occurred with concurrent requests. ### Schedule (jgitta crontab on jellyfin) | Time | Container | Command | |---|---|---| | 4:00 AM | radarr | `watchtower-update.sh radarr` | | 4:03 AM | sonarr | `watchtower-update.sh sonarr` | | 4:06 AM | prowlarr | `watchtower-update.sh prowlarr` | | 4:09 AM | flaresolverr + others | `watchtower-update.sh` (no scope) | ### Update script Location: `/usr/local/bin/watchtower-update.sh` on jellyfin host. To view: `ssh jellyfin cat /usr/local/bin/watchtower-update.sh` To edit cron: `ssh jellyfin crontab -e` Each run spins up a temporary `containrrr/watchtower` container with `--run-once`, checks for updates, sends an email notification if anything changed, and exits. The container is automatically removed after each run (`--rm`). ### Scope labels radarr, sonarr, and prowlarr each carry a `com.centurylinklabs.watchtower.scope=` label in the compose file. This tells Watchtower to only check the matching container when that scope is set. flaresolverr has no scope label and is handled by the unscoped (4:09 AM) run. --- ## Registry Authentication Credentials are stored in `/home/jgitta/.docker/config.json` on the jellyfin host. The update script mounts this file into each Watchtower container. Both `lscr.io` and `ghcr.io` are authenticated (lscr.io proxies to ghcr.io for the actual registry API, so both need credentials). Token: no-expiry `read:packages` GitHub PAT — see credentials.md. To re-authenticate (e.g. after rotating token): ```bash ssh jellyfin echo | docker login lscr.io -u jgitta --password-stdin echo | docker login ghcr.io -u jgitta --password-stdin ``` No script or compose changes needed — the config.json is read at runtime.