JellyGlance is a free, open-source command centre for your Jellyfin server. Live sessions, requests, users, libraries, watch statistics, release calendars, download queues, health checks and webhooks all sit in one clean dashboard, instead of a pile of browser tabs. It is built by me, it is inspired by Jellystat, and it will always be free: no adverts, no subscriptions and no SSO tax.
In this guide we will run JellyGlance in Docker using the official image with its PostgreSQL database, connect it to Jellyfin, and get it ready to sit behind a reverse proxy.
ghcr.io/nerdy-technician/jellyglance. Want a look first? There is a read-only live demo.✅ Before you start
- A Linux host with Docker and Docker Compose v2 installed (
docker compose versionshould work). Usedocker composewith a space: the old Pythondocker-composev1 can crash on modern Docker. - A working Jellyfin server and an admin account on it, so you can create an API key.
- One free port,
3000, for the web UI. - Optional: Sonarr, Radarr, Lidarr, Jellyseerr, Wizarr, Tdarr, qBittorrent or Transmission. JellyGlance can pull calendars, requests, invites, transcodes and download queues from all of them.
STEP 1: User and group IDs (and your secrets)
Good news: unlike the linuxserver.io images, neither JellyGlance nor the official PostgreSQL image uses PUID or PGID, so there are no IDs to look up. Postgres sets the ownership of its own data folder on first start.
What you do need are two secrets: a database password and a JWT_SECRET, which JellyGlance uses to sign logins. Generate two long random strings on your Docker host:
openssl rand -hex 24 # database password
openssl rand -hex 32 # JWT_SECRETKeep them somewhere safe, such as your password manager. In the examples below they appear as change-me and change-me-to-a-long-random-secret.
JWT_SECRET the same once you have set it. If it changes, everyone is signed out on the next restart.STEP 2: Create the folders
I like to keep every app's data under one parent folder so backups are a single job. Create the folders for JellyGlance:
mkdir -p /path/to/appdata/jellyglance/{config,backups,postgres}When you are done, the layout should look like this:
/path/to/appdata/jellyglance/
docker-compose.yml
config/ # runtime config and local app files
backups/ # backup exports and restore uploads
postgres/ # the PostgreSQL databaseFolder layout
Finally, this guide puts the app on the shared media Docker network used by the rest of my media stack, so other containers can reach it by name. If you followed the Jellyfin guide you already have it; otherwise create it once:
docker network create mediaSTEP 3: The compose file
Create /path/to/appdata/jellyglance/docker-compose.yml and paste in the following. It is the compose file from the JellyGlance repository with absolute paths and the shared network added. Use the copy button in the top right corner of the block.
services:
jellyglance-db:
image: postgres:18-alpine
container_name: jellyglance-db
restart: unless-stopped
shm_size: "1gb"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: change-me # database password from STEP 1
POSTGRES_DB: jellyglance
volumes:
- /path/to/appdata/jellyglance/postgres:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready --dbname=jellyglance --username=postgres"]
interval: 10s
timeout: 5s
retries: 5
jellyglance:
image: ghcr.io/nerdy-technician/jellyglance:latest
container_name: jellyglance
restart: unless-stopped
depends_on:
jellyglance-db:
condition: service_healthy
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: change-me # same password as above
POSTGRES_IP: jellyglance-db
POSTGRES_PORT: 5432
POSTGRES_DB: jellyglance
JWT_SECRET: change-me-to-a-long-random-secret # from STEP 1
TZ: Europe/London
CONFIG_DIR: /app/config
BACKUP_DIR: /app/backups
volumes:
- /path/to/appdata/jellyglance/config:/app/config
- /path/to/appdata/jellyglance/backups:/app/backups
ports:
- "3000:3000" # web UI and API
networks:
- default
- media
networks:
media:
external: truedocker-compose.yml
From PostgreSQL 18 the official image keeps its data in a version-numbered subfolder (
18/docker) and expects the mount one level up. Older examples that mount .../data will not keep your database where you expect with a postgres:18 image. If you are already running JellyGlance on Postgres 16 or 17, keep your existing image tag and mount; switching major versions needs a dump and restore.STEP 4: The settings explained
| Setting | What it does |
|---|---|
postgres:18-alpine |
The database JellyGlance stores everything in. It only needs to talk to JellyGlance, so it stays on the stack's private default network and is never published. |
shm_size |
Gives Postgres 1 GB of shared memory for bigger queries, as in the official compose file. |
POSTGRES_PASSWORD |
The database password. It must be the same in both services. |
POSTGRES_IP / POSTGRES_PORT |
Where JellyGlance finds the database: the jellyglance-db container on port 5432. |
JWT_SECRET |
Signs login sessions. Required, long, random and stable. |
TZ |
Time zone, so schedules, calendars and stats use your local time. List of zones. |
/app/config |
Runtime config and local app files. |
/app/backups |
Backups you create in JellyGlance appear here. To restore, drop a backup JSON file in this folder or upload it from the Backup page. |
depends_on + healthcheck |
JellyGlance waits until Postgres reports healthy before it starts. |
networks |
JellyGlance joins default (to reach Postgres) and media (to reach Jellyfin, the *arr apps and download clients by name). |
JellyGlance reads any variable from a file if you prefix it with
FILE__, for example FILE__JWT_SECRET: /run/secrets/jwt_secret with a Docker secret mounted at that path.STEP 5: Start JellyGlance
From the folder containing your compose file:
cd /path/to/appdata/jellyglance
docker compose up -d
docker compose logs -fWait until the logs settle down (press Ctrl+C to stop following them; the container keeps running), then check it is up:
docker ps --filter name=jellyglanceYou should see both jellyglance-db and jellyglance. After a minute the app should read Up ... (healthy); the image has its own health check.
STEP 6: First-run setup
First, create an API key in Jellyfin: Dashboard → API Keys → +, name it JellyGlance and copy the key. Then open http://192.168.1.20:3000 in your browser (use your Docker host's IP) and follow the setup wizard:
- Enter your Jellyfin server URL, for example
http://jellyfin:8096if both are on themedianetwork, orhttp://192.168.1.20:8096. - Paste the Jellyfin API key so JellyGlance can validate the server and sync users, libraries, artwork, sessions and activity.
- Choose your admin access mode: Jellyfin Quick Connect, an OIDC provider, or a local JellyGlance admin with a strong password.
- Connect integrations you want ready from day one (you can do this later under Settings → Integrations).
- Optionally import history from Jellystat or a Tautulli backup.
- Let the first sync finish. On a big library this can take a few minutes.
Afterwards, Settings → Integrations is where Sonarr, Radarr, Lidarr, Jellyseerr, Wizarr, Tdarr and your download clients live. On the media network, use container names such as http://sonarr:8989 plus each app's API key.
STEP 7: Put JellyGlance behind a reverse proxy
JellyGlance serves its web UI, API and live updates (Socket.IO) on port 3000, so the proxy must allow WebSockets. Use a dedicated hostname such as glance.example.com; serving it on a sub-path is not supported by these examples. Keep PostgreSQL private.
Option A: Traefik (Docker labels)
If Traefik runs on the same Docker host and is attached to the media network, add these labels to the jellyglance service. They assume your HTTPS entry point is called websecure and your certificate resolver is called cloudflare; change them to match your Traefik config.
labels:
- traefik.enable=true
- traefik.http.routers.jellyglance.rule=Host(`glance.example.com`)
- traefik.http.routers.jellyglance.entrypoints=websecure
- traefik.http.routers.jellyglance.tls.certresolver=cloudflare
- traefik.http.services.jellyglance.loadbalancer.server.port=3000
- traefik.docker.network=mediadocker-compose.yml (extra lines)
Option B: Traefik (file provider)
If Traefik runs on a different machine, add a router and service to your dynamic config file instead:
http:
routers:
jellyglance:
rule: Host(`glance.example.com`)
entryPoints:
- websecure
tls:
certResolver: cloudflare
service: jellyglance
services:
jellyglance:
loadBalancer:
servers:
- url: http://192.168.1.20:3000dynamic.yml
Option C: Nginx Proxy Manager
Add a Proxy Host for glance.example.com with scheme http, forward hostname jellyglance (if NPM is on the media network) or 192.168.1.20, and forward port 3000. Request a certificate on the SSL tab and tick Force SSL. Turn Websockets Support on and leave Cache Assets off. If large restore uploads fail with 413, add client_max_body_size 100m; under the host's Advanced tab.
The extra traefik.docker.network=media label matters here: JellyGlance is on two networks, and without it Traefik may try to reach it over the private database network.
Once everything goes through the proxy, you can remove the ports: section entirely, or limit it to the host with "127.0.0.1:3000:3000". JellyGlance recognises the public hostname from the forwarded headers, so you do not need to set any CORS options for a dedicated hostname. The reverse proxy docs have full Caddy and plain Nginx examples too.
Updating JellyGlance
The latest tag always follows the newest stable release. To update:
cd /path/to/appdata/jellyglance
docker compose pull
docker compose up -d
docker image prune -f/path/to/appdata/jellyglance/backups) and read the release notes. Do not change the postgres major version at the same time.Troubleshooting
JellyGlance keeps restarting or says it cannot reach the database. Check docker logs jellyglance-db. The password must match in both services; if you change it after the first start, Postgres keeps the old one, so either change it inside Postgres or start again with an empty postgres folder.
Postgres complains about data in /var/lib/postgresql/data. You are mounting the old path with a Postgres 18 image. Mount /var/lib/postgresql as shown in STEP 3.
The page loads but live updates stop. Your proxy is not passing WebSockets. Turn on WebSocket support (or the Upgrade/Connection headers in Nginx) and do not cache /socket.io/.
Everyone is signed out after a restart. JWT_SECRET changed. Set it once and leave it alone.
Jellyfin validation fails. From the JellyGlance container, the Jellyfin URL must be reachable: use the container name on the shared network or the host IP, never localhost.
port is already allocated when starting. Something else is using 3000 (lots of apps like it). Change the left-hand side of the port mapping, for example "3001:3000".
That's it! Your whole Jellyfin setup, at a glance. If you build something cool with it, come and say hello on the JellyGlance Discord. 🪼