Skip to content

Installation

narratorr-requests ships as a single container (the Fastify API serves the built SPA). The SQLite database lives on a volume and migrations apply automatically on boot.

  • Docker (recommended) — or Node.js 24.10+ with pnpm for a source install.
  • A running narratorr instance and an API key for it (narratorr → Settings → Security). You connect to it after first boot, in narratorr-requests’s Settings page.
  • A session secret for production (openssl rand -hex 32).
services:
narratorr-requests:
image: narratorr/narratorr-requests:latest
container_name: narratorr-requests
ports:
- "3000:3000"
volumes:
- narratorr-requests-data:/data
environment:
- NODE_ENV=production
- AUTH_BYPASS=0
# REQUIRED in production. Generate: openssl rand -hex 32
- SESSION_SECRET=change-me
# Pick at least one auth method (see Authentication):
- LOCAL_AUTH=true
restart: unless-stopped
volumes:
narratorr-requests-data:
Terminal window
docker compose up -d

Open http://localhost:3000. The first user to sign in becomes the admin — continue to First boot.

To just confirm the image runs, start it in dev-bypass mode:

Terminal window
docker run --rm -p 3000:3000 \
-e NODE_ENV=development \
-e AUTH_BYPASS=1 \
-e ALLOW_INSECURE_AUTH_BYPASS=1 \
narratorr/narratorr-requests:latest

The image defaults to NODE_ENV=production and binds 0.0.0.0, so the smoke test opts into bypass explicitly: AUTH_BYPASS=1 seeds a dev admin (every request is that admin, so you land straight in the UI) and ALLOW_INSECURE_AUTH_BYPASS=1 acknowledges the non-loopback bind. (A bare docker run with no environment exits immediately with SESSION_SECRET is required in production — by design.) GET /api/health reports readiness (it pings the DB and reports narratorrConfigured), and the container ships a matching HEALTHCHECK. Configure narratorr in Settings to actually request anything.

The connector config (narratorr URL/key and your notifiers) lives in the Settings page, not the environment — so the env surface is deliberately just server + auth + secrets.

VariableDefaultPurpose
SESSION_SECRETHMAC key for session cookies. Required in production (dev auto-generates an ephemeral one). Supports the _FILE convention.
SETTINGS_KEYderived from SESSION_SECRETDedicated key for encrypting connector secrets at rest. Set a stable one to decouple them from session-secret rotation. Supports the _FILE convention.
NODE_ENVdevelopmentproduction enables the prod posture (secure cookies, AUTH_BYPASS refusal, etc.).
BEHIND_TLSdefaults to NODE_ENV=productionWhether a reverse proxy / load balancer terminates TLS in front of the app. Gates the CSP upgrade-insecure-requests directive, the HSTS header, and the Secure session cookie flag. Set to false if running production over plain HTTP with no TLS in front — otherwise the browser silently blocks page assets (blank page) and the Secure cookie prevents login. See Troubleshooting.
AUTH_BYPASSfalseDev shortcut — seeds a dev admin, every request is that admin. Refused in prod / non-loopback.
ALLOW_INSECURE_AUTH_BYPASSfalseEscape hatch to allow AUTH_BYPASS on a non-loopback bind.
LOCAL_AUTHtrueEnable local email/password auth.
OIDC_PROVIDERSComma-separated OIDC provider ids; each configured with OIDC_<ID>_*. See Authentication. OIDC_<ID>_CLIENT_SECRET supports the _FILE convention too.
BOOTSTRAP_ADMINPin admin to <provider>:<subjectOrUsername>; disables first-user-auto-admin.
TRUSTED_PROXIESoffTrust X-Forwarded-* for the real client IP behind a reverse proxy (for rate-limit keying).
BIND_HOST127.0.0.1Listen interface (the Docker image sets 0.0.0.0).
CORS_ORIGINhttp://localhost:5173 (dev)Allowed browser origin for API calls — set it to your public URL in production.
DATABASE_PATH/data/narratorr-requests.db (image)libSQL database file. Source installs default to ./narratorr-requests.db resolved against the app root (not your current directory).
PORT3000HTTP port.
INSTANCE_BADGEDisplay-only badge for a secondary instance (e.g. dev): tints the browser-tab favicon violet and prefixes the title ([dev] Requests) so the tab is distinguishable from prod. Unset = no change.

The default request quota is not an environment variable — it’s admin-editable in Settings so it can change without a redeploy.

See .env.docker.example in the repo for a copy-paste production template, including the full OIDC variable pattern.

Instead of passing SESSION_SECRET, SETTINGS_KEY, or an OIDC CLIENT_SECRET as a plain environment variable (readable via docker inspect / /proc/<pid>/environ), point <NAME>_FILE at a file mounted read-only and the app reads the value from there instead — it never enters the process environment:

services:
narratorr-requests:
# ...
environment:
- SESSION_SECRET_FILE=/run/secrets/session_secret
- SETTINGS_KEY_FILE=/run/secrets/settings_key
- OIDC_PLEX_CLIENT_SECRET_FILE=/run/secrets/oidc_plex_client_secret
secrets:
- session_secret
- settings_key
- oidc_plex_client_secret
secrets:
session_secret:
file: ./secrets/session_secret.txt
settings_key:
file: ./secrets/settings_key.txt
oidc_plex_client_secret:
file: ./secrets/oidc_plex_client_secret.txt

Behavior:

  • The file’s contents are trimmed (a trailing newline from echo/editors won’t corrupt the value).
  • <NAME>_FILE takes precedence over the plain <NAME> if both are set.
  • An explicitly-set _FILE that’s unreadable fails fast at startup, naming the variable and path — never the file contents. For SESSION_SECRET_FILE / SETTINGS_KEY_FILE an empty file also fails fast rather than silently yielding an empty secret; for OIDC_<ID>_CLIENT_SECRET_FILE an empty file means “no client secret” (that one’s optional).
  • Fully additive: leave _FILE unset and behavior is unchanged from a plain env var.
  1. Open the app and sign in — create a local account, or use an OIDC provider if you configured one.

  2. The first user becomes admin + active automatically. (Pin a specific identity instead with BOOTSTRAP_ADMIN — see Authentication.)

  3. Go to Settings and enter your narratorr connection (Server URL + API key) — use Test to verify, then Save. See Configuration.

  4. Optionally add a notifier (ntfy, email, Discord, Slack, Telegram, Pushover, Gotify, or a generic webhook) so you’re pinged about new requests, signups, and failed requests.

  5. If your narratorr keeps companion EPUBs, optionally turn on eBook support so users can download them or send them to a Kindle.

CI builds a multi-arch (amd64/arm64) image and pushes to Docker Hub narratorr/narratorr-requests and GHCR ghcr.io/tjiddy/narratorr-requests:

  • Release — a semver tag → :latest, :X.Y.Z, :X.Y.
  • Bleeding edge — every push to develop:develop.

Migrations are forward-only (no down-migrations) and apply automatically on boot. Before upgrading a populated instance, snapshot the data volume / host directory so you can roll back by restoring it and redeploying the previous image (an older image can’t run against a newer schema).

  1. Clone and install

    Terminal window
    git clone https://github.com/tjiddy/narratorr-requests.git
    cd narratorr-requests
    pnpm install
  2. Build

    Terminal window
    pnpm build
  3. Run (set SESSION_SECRET + an auth method in the environment first)

    Terminal window
    node dist/server/index.js