Containers#
NixOS container modules live under modules/nixos/containers/. Each runs an
OCI workload via Podman (virtualisation.oci-containers.backend = "podman",
dockerCompat = true) using the compose2nix pattern: explicit per-container
systemd units, dedicated podman-network-*/podman-volume-* oneshots, and a
podman-compose-*-root.target that ties them together.
Only three of these modules are wired into the flake. modules/nixos/default.nix
imports:
./containers/doc-pipeline./containers/esphome./containers/open-webui
The containers/n8n and containers/test directories also exist but are not
imported anywhere — they are raw compose2nix output (see the
# Auto-generated using compose2nix headers) and are inert until added to the
imports list. The three imported modules are enabled on ereshkigal.
tsunaminoai.docPipeline#
Paperless-NGX document archive plus its LLM sidecars, defined in
modules/nixos/containers/doc-pipeline/default.nix.
Containers in the paperless_default Podman network:
- paperless-web (
ghcr.io/paperless-ngx/paperless-ngx:latest) — the web UI, on host portpaperlessPort(default8011, → container8000). - paperless-db (
postgres:17-alpine) and paperless-broker (valkey/valkey:9-alpine) — internal-only Postgres and Redis-compatible broker. - paperless-gpt (
ghcr.io/icereed/paperless-gpt:latest) — optional sidecar (paperlessGpt.enable, defaulttrue) for LLM auto-tagging/titling and vision OCR, on host portpaperlessGpt.port(default8013). - anythingllm (
mintplexlabs/anythingllm:latest) — optional RAG chat over the library (anythingLlm.enable, defaulttrue), on host portanythingLlm.port(default13001).
Inference is delegated to Ollama on another host via the ollamaHost option
(use mokou’s Tailscale FQDN); ollamaModel (default qwen2.5vl:7b) is the
vision model. Containers reach that host through the host routing table via
--add-host=host.containers.internal:host-gateway.
The module NFS-mounts voile’s document share (voileSharePath, default
/volume2/Books) at /mnt/voile/documents over the 10G backhaul and uses
consume/export subdirectories there for ingest and archival copies.
Paperless state is staged into /var/backup/paperless by borgmatic
before_backup hooks (Postgres dump plus snapshots of the paperless_media and
paperless_data volumes) for the existing borg job to voile.
The paperless-gpt API token comes from the sops secret paperless/api-token,
formatted into a sops.templates env file and injected via the systemd
EnvironmentFile — never inlined.
tsunaminoai.esphome#
ESPHome dashboard/builder, defined in
modules/nixos/containers/esphome/default.nix.
Runs ghcr.io/esphome/esphome:latest with --network=host so it can reach IoT
devices on a separate VLAN and participate in mDNS on the LAN. Key options:
port(default6052) — dashboard web UI, opened only onlanInterface(defaultvmbr0).configDir(default/var/lib/esphome) — mounted as/config. Managed device configs are bind-mounted in directly (ESPHome rejects/nix/storesymlinks);secrets.yamlmust be placed manually inconfigDir.iotVlan.{network,prefixLength,gateway}— a static route is installed vianetworking.localCommandsso the host forwards IoT VLAN traffic to the gateway.
Avahi is enabled on lanInterface for LAN-side mDNS discovery.
tsunaminoai.openWebui#
Open-WebUI LLM chat + RAG knowledge base, defined in
modules/nixos/containers/open-webui/default.nix.
Runs ghcr.io/open-webui/open-webui:main on host port port (default 3000,
→ container 8080) in the openwebui_default network. It talks to Ollama via
ollamaHost (mokou over Tailscale) for both chat and nomic-embed-text
embeddings, with an embedded ChromaDB vector store. Login is required
(WEBUI_AUTH = "true"; first registered user becomes admin).
A nightly systemd timer (paperless-openwebui-sync, OnCalendar = 02:00) pulls
already-OCR’d Paperless documents into an Open-WebUI knowledge collection. It
reads the sops secrets paperless/api-token and openwebui/api-key.
TLS reverse proxy#
When tsunaminoai.pki.acme.enable is set, doc-pipeline and open-webui add nginx
virtual hosts serving each UI over HTTPS on port + 1, using the host’s
step-ca ACME certificate (useACMEHost = <hostname>.<tailscaleDomain>). All
three modules register their UIs as Homer dashboard entries.
Torrent traffic routing#
Routing torrent traffic out over Tailscale is not done with network
namespaces. It is handled by modules/nixos/servarr/tailscale-routing.nix,
which enables IP forwarding and installs iptables mangle rules that mark
BitTorrent/qBittorrent/Transmission ports (and the media user’s traffic) with
fwmark 1, then routes marked packets out tailscale0 via a dedicated routing
table (ip rule add fwmark 1 table 100). See the servarr module for the full
stack.