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

181 lines
6.4 KiB
Markdown

# 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
```yaml
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`)
```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)
```yaml
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.