Documents Stack

Home

Services running on PCT 104 (documents, 192.168.2.105). Docker storage on ZFS (/tank/docker). Only /tank/media and /tank/appdata/nextcloud are bind-mounted — not the full tank.


Service URLs

Service URL Port Stack
Immich photos.carr-family.org 2283 documents-immich (compose 10)
Nextcloud cloud.carr-family.org 8085 documents-nextcloud (compose 7)
Paperless-ngx paperless.carr-family.org 8000 documents-paperless (compose 4)
paperless-gpt paperless-gpt.carr-family.org 8080 documents-paperless (compose 4)
Linkwarden links.carr-family.org 3000 documents-linkwarden (compose 2)
Actual Budget actual.carr-family.org 5006 documents-actual-budget (compose 28)
OtterWiki otterwiki.carr-family.org 8081 standalone (not Swarm)

Immich (compose 10)

Photo management at photos.carr-family.org. Services: immich-server, machine-learning, postgres/pgvecto.rs (port 5435), valkey.

GPU: uses -cuda image (ghcr.io/immich-app/immich-machine-learning:release-cuda) with DEVICE=cuda + NVIDIA_VISIBLE_DEVICES=all. nvidia-container-toolkit installed in PCT 104; Docker default runtime is nvidia.

Redeploy (env vars stored in Portainer DB — export in shell):

pct exec 108 -- bash -c 'export DB_PASSWORD=T5dBbAvgHWceH7jhsxjism4b2Cre9NA DB_USERNAME=immich DB_DATABASE_NAME=immich && docker stack deploy -c /mnt/tank/appdata/portainer/compose/10/docker-compose.yml documents-immich'

Nextcloud (compose 7)

File cloud at cloud.carr-family.org. Services: app (port 8085), cron, postgres:16 (port 5432).

  • Nextcloud data on ZFS at /mnt/tank/appdata/nextcloud/ (migrated 2026-05-25)
  • PUID/PGID: 33:33
  • Obsidian vault lives inside the Nextcloud data tree: /tank/appdata/nextcloud/data/data/nextcloud/files/obsidian (bind-mounted into PCT 107 and PCT 109)

Paperless-ngx (compose 4)

Document management at paperless.carr-family.org. Services: webserver (port 8000), paperless-gpt (port 8080), redis, postgres (port 5433).

Redeploy (env vars not in .env):

pct exec 108 -- bash -c 'export POSTGRES_DB=paperless POSTGRES_USER=paperless POSTGRES_PASSWORD=eWJarhDasRNBu0LfBwuI6VPOXwnRCUy PAPERLESS_SECRET_KEY=J60Om5l2dsL1pUSWz3AQhqizRFhlii8dRzsynYrAfzbBsV316S PAPERLESS_TIME_ZONE=America/Chicago PAPERLESS_URL=https://paperless.carr-family.org "PAPERLESS_CSRF_TRUSTED_ORIGINS=https://paperless.carr-family.org" && docker stack deploy -c /mnt/tank/appdata/portainer/compose/4/docker-compose.yml documents-paperless'

Paperless Gotchas

Postgres password gotchaPOSTGRES_PASSWORD only applies on first-init. If the DB container is recreated (e.g. Watchtower update) with existing data, the stored password is unchanged but a mismatch causes password authentication failed crash-loop. Fix via local socket (trust auth bypasses password check):

DB=$(pct exec 104 -- docker ps --format '{{.Names}}' | grep 'paperless_db\.' | head -1)
pct exec 104 -- docker exec $DB psql -U paperless -d paperless -c "ALTER USER paperless PASSWORD 'newpassword'"
pct exec 108 -- docker service update --force documents-paperless_webserver

Note (2026-05-29): DB passwords and Paperless secret key regenerated after Portainer lost stack env vars. Data intact; sessions invalidated.

paperless-gpt

LLM-powered title/tag suggestions + OCR at paperless-gpt.carr-family.org (port 8080). Image: icereed/paperless-gpt:latest.

  • General LLM: gpt-4o-mini via LiteLLM (http://192.168.2.83:4000)
  • OCR LLM: llava:7b via LiteLLM → Ollama on 192.168.2.11 (RTX 3060). llama3.2-vision:11b failed — Ollama 0.30.7 on .11 doesn't support mllama arch.
  • Trigger: tag a document paperless-gpt-ocr to trigger OCR processing
  • API token: 71c8ba36985118d419cf9a1f5f8497290d816e10

Linkwarden (compose 2)

Bookmark manager at links.carr-family.org. Services: linkwarden (port 3000), meilisearch, postgres (port 5434).

API token:

eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..jE4UUgAXi4UpbmBi.1HbSWJSBdDAHur9EAI28WT2Cbt4rJqzakS_RrjQq9jT34iRJBb7nEW97Ml218sECdaShktMrdxAXrl6zQFXBkM4G2jpWtSEOUlIKlOH0v1KAtXRfpb9b.tNDkqQ1JdOz6gALxNDKhVA

PUT /api/v1/links/:id gotcha — payload must include collection: {id, name, ownerId} and tags as [{id, name}]. Omitting either returns 400: expected number, received undefined [id].

Auto-sort Script (PCT 109)

  • Script: /opt/linkwarden-sort/sort.py
  • Cron: */2 * * * * — runs every 2 minutes, logs to /var/log/linkwarden-sort.log

Moves new links out of Unorganized (id 1) automatically. Categorization priority:

  1. AI tags (Linkwarden auto-tags on save)
  2. URL patterns — known subreddits, makerworld.com, kemono.cr, etc.
  3. TikTok — follows redirect to get @username, maps known creators; falls back to username pattern matching
  4. Title/meta keywords

To add new rules, edit TAG_RULES, URL_RULES, TITLE_RULES, or TIKTOK_USER_RULES at the top of the script.

Collection IDs

ID Name
1 Unorganized
4 Minecraft
7 3D-Printing
8 Homelab
9 Self-Host
10 Tabletop/Board Games
11 Video Games
14 Art
17 Satisfactory
18 Dungeons and Dragons
19 Pokemon

OtterWiki (standalone — not Swarm)

This wiki. Docker Compose on PCT 104 at /mnt/tank/appdata/otterwiki/docker-compose.yml.

  • Image: redimp/otterwiki:2
  • Port: 8081 (host-mode on 192.168.2.105)
  • Static route: Traefik routes.ymlhttp://192.168.2.105:8081
  • Repository: /mnt/tank/appdata/otterwiki/repository/ — all pages stored as .md files, tracked in git
  • PUID/PGID: 33:33

Restart:

pct exec 104 -- docker compose -f /mnt/tank/appdata/otterwiki/docker-compose.yml restart

To edit pages directly via git (e.g. bulk imports):

git config --global --add safe.directory /mnt/tank/appdata/otterwiki/repository
cd /mnt/tank/appdata/otterwiki/repository
git add -A && git commit -m "message"

OtterWiki picks up committed changes immediately on next page load.