Initial commit
This commit is contained in:
commit
38c24fc9dc
166 files changed
+22002
No files matched your search
@@ -0,0 +1,180 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user