Skip to content

Nocturne preview drain

The preview drain is in effect. The gallery writer is nocturne-gallery on CaptainKangapoo (192.168.3.17), healthy, published on 0.0.0.0:8427. Workers are 10, with a 12 CPU cap and a 6g memory limit. saltierpoop's nocturne-gallery is exited with restart=no. Leave that as the required state while the PC is the writer.

Why this shape

The gallery process is the worker pool. The share .env still has GALLERY_WORKERS=4 and GALLERY_CPUS=4.0. The PC override sets GALLERY_WORKERS=10, cpus: "12.0", and mem_limit: 6g, and those win. There is no separate worker to send away. Ten single-thread ffmpeg jobs under a 4 CPU cap would not drain faster. Docker on this PC sees 32 CPUs and about 46 GiB, so the container cap is 12: enough for the ten workers plus uvicorn, with the rest left for the OS and Docker. The image's worker_count() clamps the env at 8. The Prawns share is read-only, so the override bind-mounts /home/someone/nocturne-drain-library.py over /app/library.py with that ceiling raised to 10. At 4 workers the container was using about 1.5 GiB of a 2g limit, mostly file cache (anonymous memory was under 500 MB). 6g covers ten ffmpeg jobs without an oversized limit.

CaptainKangapoo (192.168.3.17) is off prox. The personal VLAN can mount 192.168.6.215:/volume9/Prawns over NFSv4. The PC has no SSH and no Docker API (SMB, RDP, and Ollama only), so the PC steps below are run on that machine, not from infra-services.

Public route

This drain's public route is the PC via /opt/traefik/nocturne.yml on saltierpoop, with Authentik in front. The service URL is http://192.168.3.17:8427 (it was http://nocturne-gallery:8427). TLS and the Host rule are unchanged. Websecure middlewares include authentik@docker after cloudflarewarp@docker, plus the previous header middlewares. https://nocturne.realemail.app returns 302 to Authentik when the browser has no session.

Authenticated sessions reach the gallery. Uvicorn trusts X-Forwarded-Proto only from the TCP peer in FORWARDED_ALLOW_IPS. The share .env still has GALLERY_TRUSTED_PROXIES=172.19.0.0/16 (the saltbox bridge). On the PC, Docker publishes 8427 through the bridge gateway 172.20.0.1, so Traefik's X-Forwarded-Proto: https was ignored, the app treated the request as HTTP, and it returned 308 to https://nocturne.realemail.app/ (the same URL). Traefik added x-content-type-options and x-robots-tag on the way out. cloudflarewarp does not rewrite the scheme for non-Cloudflare LAN clients. The drain override sets FORWARDED_ALLOW_IPS to 172.19.0.0/16,172.20.0.0/16,192.168.6.0/24. A trusted X-Forwarded-Proto: https then returns 200 from the app. A direct HTTP request with no forwarded proto still 308s.

The route depends on UniFi policy 10011 (Saltierpoop -> CaptainKangapoo 4 Nocturne). It allows 192.168.6.243 → 192.168.3.17 TCP 8427, with the port on the destination, the same shape as policy 10007. Rule 30012 is only the derived established/related return. Block inter-VLAN is 40000. Leave policy 10011 in place while the PC might run the gallery again. Do not delete it on move-back, and do not add other firewall edits for this drain.

Do not

  • Do not edit gallery/compose.yaml on the Prawns share. It joins the external saltbox network, which exists only on saltierpoop.
  • Do not put /opt/nocturne-gallery on NFS. The catalog is SQLite and the gallery requires that state on local disk.
  • Do not run two containers. Two writers corrupt /data.
  • Do not start saltierpoop nocturne-gallery while the PC container is the writer. Required state on saltierpoop until step 7: exited, restart=no.
  • Do not point Traefik at http://nocturne-gallery:8427 before that container exists. Do not leave the service URL on http://192.168.3.17:8427 after the saltierpoop container is the writer.
  • Do not stop the saltierpoop container until the PC has the library mounted and a local directory ready for the state copy.

Layout

Piece Where
Compose and image source /mnt/local/Media/Prawn/4. Reddit/_reddit-explorer/gallery on the Prawns NFS share
State on saltierpoop /opt/nocturne-gallery (idle while the PC is the writer)
State on the PC /home/someone/nocturne-gallery in WSL Ubuntu
Library on the PC NFSv4, read-only, at /mnt/prawns
Publish 0.0.0.0:8427 on the PC. saltierpoop's container is exited
Public route /opt/traefik/nocturne.yml → http://192.168.3.17:8427, Authentik, UniFi policy 10011
PC override nocturne-drain.compose.yaml
Worker ceiling /home/someone/nocturne-drain-library.py bind-mounted over /app/library.py

The override drops saltbox, publishes 8427 on all PC interfaces, sets 10 workers, a 12 CPU cap, and a 6g memory limit, bind-mounts the local library.py that raises the image ceiling from 8 to 10, and sets FORWARDED_ALLOW_IPS so Uvicorn trusts the PC docker gateway (172.20.0.1) plus 192.168.6.0/24 and the old saltbox range.

Drain

Steps 1–6 are already done. Do not repeat them while the PC is the writer. Step 7 is the move back.

  1. On CaptainKangapoo, mount the Prawns library with NFSv4, for example 192.168.6.215:/volume9/Prawns. Confirm gallery/compose.yaml is visible. If the mount fails, stop. Leave saltierpoop running.
  2. On the PC, create a local state directory, not a path on the NFS mount. The live copy is /home/someone/nocturne-gallery in WSL Ubuntu.
  3. On saltierpoop, stop nocturne-gallery and set restart=no. That is the CPU win. The public URL stays on the PC only after /opt/traefik/nocturne.yml points at http://192.168.3.17:8427 and UniFi policy 10011 allows TCP 8427 to that host.
  4. Copy /opt/nocturne-gallery to the PC local directory (rsync -a). One side only. Do not start the saltierpoop container again while the PC copy is the writer.
  5. On the PC, from the gallery directory on the share, with GALLERY_STATE_ROOT set to that local directory and GALLERY_MEDIA_ROOT set to the read-only Reddit library (/mnt/prawns):
docker compose -f compose.yaml -f nocturne-drain.compose.yaml up -d --build

Do not load the saltierpoop .env bind address (GALLERY_BIND=192.168.6.243). The override replaces the published port. 6. Keep the PC awake until previews catch up. Sleep pauses the queue the same way a CPU cap would. 7. Move the writer back to saltierpoop. One writer the whole time, and Traefik stays on the PC address until the saltierpoop container exists. Do not point the file at http://nocturne-gallery:8427 while that container is down, and do not leave it on http://192.168.3.17:8427 after the name-based service is up. Leave UniFi policy 10011 in place.

  1. Stop the PC container. Do not start saltierpoop yet.
  2. rsync -a /home/someone/nocturne-gallery back to /opt/nocturne-gallery on saltierpoop. One side only.
  3. Start nocturne-gallery on saltierpoop and set its restart policy to unless-stopped. Traefik is still aimed at the stopped PC, so this start is not a second writer.
  4. Set the Traefik service URL in /opt/traefik/nocturne.yml back to http://nocturne-gallery:8427. Leave TLS, the Host rule, and the middlewares (including authentik@docker after cloudflarewarp@docker) as they are. The file provider watches that file, so the 502 window between the container start and the reload is short.

Checked 2026-10-09

  • Writer: CaptainKangapoo 192.168.3.17, container nocturne-gallery, healthy, published 0.0.0.0:8427. State is /home/someone/nocturne-gallery in WSL Ubuntu. The NFS library is read-only at /mnt/prawns. GALLERY_WORKERS=10, NanoCpus 12000000000 (12 CPUs), memory limit 6g. http://127.0.0.1:8427/health returns ok, and worker_count() is 10.
  • saltierpoop nocturne-gallery is exited with restart=no.
  • /opt/traefik/nocturne.yml serves http://192.168.3.17:8427. Unauthenticated https://nocturne.realemail.app returns 302 to Authentik. The PC override trusts 172.20.0.0/16 (docker gateway peer 172.20.0.1), so a forwarded https proto is 200 from uvicorn instead of a 308 to the same URL.
  • UniFi policy 10011 allows 192.168.6.243 → 192.168.3.17 TCP 8427 (port on the destination). Rule 30012 is the derived established/related return. Block inter-VLAN is 40000.
  • Prawns export allows 192.168.3.0/24. An NFSv4 mount from 192.168.3.37 succeeded. NFSv3 from the same host was reset.
  • From the lab, 192.168.3.17 answered SMB, RDP, and Ollama. Port 22 and the Docker API were closed, so the container was started on the PC, not over SSH from infra-services.