Media
JellyGlance logo

How to install JellyGlance with Docker

JellyGlance Docker install guide cover
Difficulty: BeginnerTime: 20 minutesTested with: JellyGlance 1.3 (official image)

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.

💡
Note: This guide works with the current JellyGlance 1.3 release (1.3.0 at the time of writing) from 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 version should work). Use docker compose with a space: the old Python docker-compose v1 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_SECRET

Keep 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.

⚠️
Warning: Keep 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 database

Folder 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 media

STEP 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: true

docker-compose.yml

💡
Note: Why /var/lib/postgresql and not /var/lib/postgresql/data?
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).
👍
Tip: Rather not keep secrets in the compose file?
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 -f

Wait 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=jellyglance

You 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:

  1. Enter your Jellyfin server URL, for example http://jellyfin:8096 if both are on the media network, or http://192.168.1.20:8096.
  2. Paste the Jellyfin API key so JellyGlance can validate the server and sync users, libraries, artwork, sessions and activity.
  3. Choose your admin access mode: Jellyfin Quick Connect, an OIDC provider, or a local JellyGlance admin with a strong password.
  4. Connect integrations you want ready from day one (you can do this later under Settings → Integrations).
  5. Optionally import history from Jellystat or a Tautulli backup.
  6. 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=media

docker-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:3000

dynamic.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
⚠️
Warning: Before a major upgrade: make a backup from the Backup page (it lands in /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. 🪼