JellyboxJellybox/Docs
← Self-hosting guide

Self-host with Docker

Run the whole Jellybox stack on your own hardware — a NAS, a Raspberry Pi, a home server, anywhere Docker runs. The repo ships a Dockerfile and a docker-compose.ymlthat bring up Postgres and the Jellybox web app together. You don't need Vercel, Neon, or any cloud account. The Vercel guide is the easier path; this is the more private one.

Before you start

  • Docker Engine 24+ and Docker Compose v2
  • A machine on the same network as your Jellyfin server
  • Optionally: a reverse proxy (Caddy, nginx, Traefik, …) if you want HTTPS / a public domain
1

Clone the repo

git clone https://github.com/Nikorag/Jellybox-Server.git
cd Jellybox-Server
2

Generate secrets

Two values must be filled in before the stack will start cleanly. The first is the NextAuth signing secret, the second is the AES key Jellybox uses to encrypt Jellyfin tokens at rest (and OAuth state for the extension framework).

# AUTH_SECRET (any 32+ char string works; this is the canonical generator)
openssl rand -base64 32

# JELLYFIN_ENCRYPTION_KEY (must be exactly 64 hex chars = 32 bytes)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Never change JELLYFIN_ENCRYPTION_KEY once your install has stored encrypted data. Rotating it invalidates every saved Jellyfin token and every in-flight extension OAuth flow. Back the value up.
3

Configure .env

Copy the Docker-flavoured template and fill in the two secrets you generated.

cp .env.docker.example .env
$EDITOR .env

The required pair is AUTH_SECRET and JELLYFIN_ENCRYPTION_KEY. Everything else is optional — email sign-up via Resend, Google or generic OIDC sign-in, the ADMINS allowlist that gates extension registration, and so on. DATABASE_URL and AUTH_URL are managed by Compose, so leave them out of .env.

4

Bring the stack up

docker compose up -d --build

On first boot the Jellybox container runs prisma migrate deploy against the empty Postgres volume, then starts Next.js on port 3000. Subsequent restarts re-apply any pending migrations idempotently.

Open http://localhost:3000(or wherever you exposed the container) and create your account. From there it's the same as any Jellybox install: link Jellyfin, pair a device, register tags.

5

Put it behind a reverse proxy (optional)

Compose binds Jellybox to 0.0.0.0:3000 on the host. For anything beyond a LAN install, terminate TLS upstream and forward to the container. Set AUTH_URL in .env to your public hostname, e.g. https://jellybox.your-home.example — NextAuth uses this to sign callback URLs.

Caddyfile example:

jellybox.your-home.example {
  reverse_proxy localhost:3000
}

If you're using Google or OIDC sign-in, add the new callback URL <AUTH_URL>/api/auth/callback/google (or /oidc) to the provider's allowed redirects.

6

Add an extension sidecar (optional)

The compose file ships a commented-out jellybox-ha-extension service that runs the Home Assistant scripts example as a sidecar on the same Docker network. Uncomment it, set HOMEASSISTANT_URL and JELLYBOX_HA_BEARER_SECRET in your .env, and register the URL http://jellybox-ha-extension:4557 at /dashboard/settings/extensions. The extension never has to be reachable from outside the Docker network — only Jellybox does.

For the contract details and how to write your own extension, see Use & build extensions.

7

Firmware OTA (optional)

By default your install treats the latest GitHub release of the upstream firmware repo (Nikorag/Jellybox-Firmware) as the available version. Paired devices poll /api/device/me every 30 seconds and report their running version; updates are triggered by hand from the device page in the dashboard — see the firmware OTA section for the full flow. The behaviour is configurable through two env vars in your .env:

# Track a different firmware repo (e.g. your own fork).
FIRMWARE_REPO=your-github-username/Jellybox-Firmware

# Pin every device to a specific release tag instead of the latest.
# Leave unset (or "latest") to always serve the newest release.
FIRMWARE_VERSION=v0.0.2

Both are optional. After changing either, restart the Jellybox container so the background fetcher picks up the new URL:

docker compose up -d
If you fork the firmware, your CI must publish a manifest.json asset on each release with at least version (string) and url (string) fields — urlis the binary the device downloads. The upstream firmware repo's release workflow is the canonical reference.
8

Backups

All persistent state lives in the jellybox-postgres named volume. Snapshot it with:

docker compose exec postgres pg_dump -U jellybox jellybox > backup-$(date +%F).sql

Restore by re-importing into a fresh volume. Your .env file (specifically JELLYFIN_ENCRYPTION_KEY) must match the one that produced the backup, otherwise stored Jellyfin tokens won't decrypt.

Updating

Pull the latest source and rebuild:

git pull
docker compose up -d --build

Migrations run automatically on container start. Rolling back a deployment that ran a migration means restoring from the backup you took before — Prisma migrations aren't reversible in place.