Files
Notizarr/docs/phase1/06-docker-unraid-design.md
2026-09-07 09:54:30 +00:00

6.4 KiB

06 — Docker / Unraid Design

Containers

Container Image Role
notizarr built locally from apps/server (multi-stage, bundles the built apps/web static assets into the final image) app + web UI
notizarr-postgres postgres:16-alpine (pinned to a specific digest/patch tag at Phase 2) database

No Redis, no worker, no reverse proxy container — Pangolin is external and already run separately per the user's existing setup.

Networking

  • Single user-defined bridge network (notizarr_internal), so notizarr reaches Postgres via the service name notizarr-postgres:5432 and Postgres is never bound to a host port.
  • notizarr publishes two host ports:
    • 8080 → web/PWA + OIDC + admin. In Unraid's port mapping, this should be bound to the LAN interface (or left for Pangolin to reach over the Docker network directly, if Pangolin also runs in Docker — recommended, see note below) rather than published on 0.0.0.0 if the host has any direct WAN exposure.
    • 8081 → LAN ingestion only. Must be bound to a LAN-only address in Unraid's port mapping (e.g. 192.168.1.10:8081:8081, not 0.0.0.0:8081:8081), and/or restricted at the router/firewall level. The app additionally enforces INGESTION_ALLOWED_CIDRS as defense in depth (see threat model).
  • Recommended topology if Pangolin also runs under Docker on the same Unraid host: attach notizarr to Pangolin's Docker network as well (or put both on a shared network) so Pangolin reaches notizarr:8080 directly over the internal network without needing 8080 published to the host at all. This is the cleanest setup and is called out as the recommended option in the deployment README written in Phase 2, while still supporting a published-port fallback for other reverse-proxy layouts.

Volumes

Host path (under /mnt/tank/appdata/notizarr/) Container path Purpose
postgres/ /var/lib/postgresql/data Postgres data directory (named volume backed by this bind mount)
app/ /data reserved for any future app-writable state; empty/unused in v1 since VAPID keys and settings live in Postgres
backups/ (used by the documented backup script, not mounted into either container at runtime) pg_dump output destination
logs/ (optional bind mount if file logging is enabled) stdout JSON logs are the default and are what docker logs / Unraid's log viewer shows; this folder exists for operators who want to additionally pipe logs to a file via docker compose's logging driver

Healthchecks

notizarr-postgres:
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U notizarr -d notizarr"]
    interval: 10s
    timeout: 5s
    retries: 5

notizarr:
  healthcheck:
    test: ["CMD", "node", "healthcheck.js"]   # small script hitting http://127.0.0.1:8080/health
    interval: 15s
    timeout: 5s
    retries: 3
    start_period: 20s

notizarr has depends_on: notizarr-postgres: condition: service_healthy, so it never starts serving traffic against a not-yet-ready database.

Restart policy, non-root, pinned versions

  • restart: unless-stopped on both services.
  • notizarr's Dockerfile creates and runs as a non-root appuser (USER appuser in the final stage); the official postgres:16-alpine image already drops to the postgres user via its entrypoint.
  • Every image reference is pinned to an explicit tag (and ideally digest) — no :latest anywhere in the compose file.

Environment variables (.env)

# Identity / URLs
PUBLIC_BASE_URL=https://notizarr.example.com
PORT_WEB=8080
PORT_INGEST=8081

# Auth
AUTH_MODE=oidc                      # oidc | proxy | disabled
OIDC_ISSUER_URL=
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=
OIDC_REDIRECT_URL=https://notizarr.example.com/api/v1/auth/oidc/callback
OIDC_SCOPES=openid profile email
AUTH_PROXY_USER_HEADER=
AUTH_PROXY_EMAIL_HEADER=
AUTH_PROXY_GROUPS_HEADER=
TRUSTED_PROXY_CIDRS=

# Database
POSTGRES_USER=notizarr
POSTGRES_PASSWORD=
POSTGRES_DB=notizarr
DATABASE_URL=postgres://notizarr:${POSTGRES_PASSWORD}@notizarr-postgres:5432/notizarr

# Push
VAPID_SUBJECT=mailto:admin@example.com

# Ingestion safety
INGESTION_ALLOWED_CIDRS=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16

# Retention / rate limiting (defaults; also editable in Admin > Settings)
RETENTION_DEFAULT_DAYS=90
RATE_LIMIT_SUSTAINED_PER_MIN=60
RATE_LIMIT_BURST_PER_10S=20

# Ops
LOG_LEVEL=info
DOCS_ENABLED=true

.env.example (committed) documents every variable; the real .env on the Unraid host is never committed and is included in the backup procedure.

Illustrative docker-compose.yml (final version written in Phase 2)

services:
  notizarr-postgres:
    image: postgres:16-alpine
    container_name: notizarr-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - ./postgres:/var/lib/postgresql/data
    networks: [notizarr_internal]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  notizarr:
    image: notizarr:latest   # built via `docker compose build`
    container_name: notizarr
    restart: unless-stopped
    depends_on:
      notizarr-postgres:
        condition: service_healthy
    env_file: .env
    environment:
      DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@notizarr-postgres:5432/${POSTGRES_DB}
    ports:
      - "8080:8080"
      - "8081:8081"
    volumes:
      - ./app:/data
    networks: [notizarr_internal]
    healthcheck:
      test: ["CMD", "node", "healthcheck.js"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 20s

networks:
  notizarr_internal:

Backup / restore

  • Backup: a documented script (docker compose exec notizarr-postgres pg_dump -U notizarr notizarr | gzip > backups/notizarr-$(date +%F).sql.gz) — since VAPID keys and all settings live in Postgres, a Postgres backup alone is a complete backup. .env is backed up separately (it's a plain file, not a container volume) since it holds the OIDC client secret and DB password.
  • Restore: docker compose down, gunzip -c backup.sql.gz | docker compose exec -T notizarr-postgres psql -U notizarr notizarr, docker compose up -d. Full runnable instructions land in the Phase 2 README.