181 lines
6.4 KiB
Markdown
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.
|