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 port paperlessPort (default 8011, → container 8000).
  • 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, default true) for LLM auto-tagging/titling and vision OCR, on host port paperlessGpt.port (default 8013).
  • anythingllm (mintplexlabs/anythingllm:latest) — optional RAG chat over the library (anythingLlm.enable, default true), on host port anythingLlm.port (default 13001).

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 (default 6052) — dashboard web UI, opened only on lanInterface (default vmbr0).
  • configDir (default /var/lib/esphome) — mounted as /config. Managed device configs are bind-mounted in directly (ESPHome rejects /nix/store symlinks); secrets.yaml must be placed manually in configDir.
  • iotVlan.{network,prefixLength,gateway} — a static route is installed via networking.localCommands so 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.