services: watchstate: image: ghcr.io/arabcoders/watchstate:${WATCHSTATE_VERSION} container_name: watchstate restart: unless-stopped # The image runs rootless and drops privileges to this uid:gid — it is a # `user:` directive, not PUID/PGID env vars like the LinuxServer images # (arr, jellyfin). It must match the owner of the appdata bind below or # the container exits on start, unable to write /config. On Unraid that is # 99:100 (nobody:users) — upstream's own Unraid note says the same. # # WatchState won't chown /config itself the way the LinuxServer images do, # so the `appdata:` block on this stack's host_vars entry pre-creates the # bind directory owned by 99:100 before `docker compose up` — otherwise # Docker makes it root:root on first start and the container exits. user: "${PUID}:${PGID}" # Unraid's Docker page normally reads icon, WebUI link and console shell # from the dockerMan template that created the container. A Compose stack # has no template, so 6.10+ falls back to these labels; without them the # container renders as a question mark with no WebUI or Console entry. # See README.md → "Icons on the Unraid Docker page". # # `sh`, not `bash`: the image is Alpine-based (frankenphp on Alpine). labels: net.unraid.docker.icon: ${STACK_ICON} net.unraid.docker.webui: "http://[IP]:${WATCHSTATE_HTTP_PORT}/" net.unraid.docker.shell: sh # Joins Caddy's network by the fixed `proxy` alias, same shape as arr and # jellyfin; the real network it maps to is a per-host decision in vars.yml # (WATCHSTATE_NETWORK → caddy-net). Caddy then proxies to # `watchstate:8080` — the container name on caddy-net — rather than nas1's # LAN IP and the published port. # # This also puts Jellyfin one hop away: WatchState reaches it as # `http://jellyfin:8096` when adding it as a backend. Plex and anything # else off caddy-net is addressed by nas1's LAN IP and its published port # (e.g. http://192.168.50.1:32400) — a runtime concern configured in the # WebUI, not here. networks: - proxy environment: # Image default is UTC; TZ comes from vars.yml. Both are named here # because Compose reads .env only to interpolate `${...}` in this file — # a var that nothing references never reaches the container. WS_TZ: ${TZ} TZ: ${TZ} # The task scheduler (import every hour, export every 90 min) and the # built-in cache server are both on by default. Uncomment to turn either # off — e.g. DISABLE_CRON while doing the initial one-off backend sync by # hand, before enabling the tasks on the Tasks page. # DISABLE_CRON: "1" # DISABLE_CACHE: "1" # --- Reverse-proxy auth (optional) -------------------------------------- # WatchState has its own internal auth (the first-run system user). If it # is ever fronted by Caddy + Authentik forward-auth instead, set these so # it trusts the proxy rather than double-prompting. Setting BOTH disables # all internal auth — only do that once the external layer is definitely # in front of it. # WS_TRUST_PROXY: "true" # WS_TRUST_LOCAL: "true" volumes: # SQLite database (db/watchstate_v01.db), backend configs, guid.yaml and # logs — this directory *is* the application state, same standing as # arr's SHELFARR_DATA_PATH. Back it up accordingly. - ${WATCHSTATE_CONFIG_PATH}:/config ports: - "${WATCHSTATE_HTTP_PORT}:8080" # Restated from the image's own HEALTHCHECK. The port here is the fixed # internal 8080, never WATCHSTATE_HTTP_PORT. healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/v1/api/system/healthcheck"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: # `external: true` means "attach to a network that already exists; don't # create it, don't delete it on `down`". Without it Compose would create a # *new* network named `watchstate_` — the deploy would still succeed and # Caddy would silently fail to resolve `watchstate`. `name:` is what stops # the project-name prefix being applied. # # The local alias is a fixed `proxy` so the compose file stays static; which # real network it maps to is a per-host decision in vars.yml # (WATCHSTATE_NETWORK). Same shape as arr's `proxy` → `caddy-net` and # jellyfin's. # # caddy-net is defined in Unraid's Docker settings on nas1 rather than by a # stack in this repo, so nothing here has to deploy before WatchState does. # It must exist on the target host — an absent external network fails the # deploy outright. proxy: name: ${WATCHSTATE_NETWORK} external: true # Reaching WatchState through Caddy means proxying to `watchstate:8080` — the # container name on caddy-net — rather than nas1's LAN IP and the published # port. The `ports:` mapping above is kept for direct access that bypasses the # proxy; drop it if you don't want WATCHSTATE_HTTP_PORT reachable on the LAN.