prometheus, grafana, loki, alloy and node-exporter ran only from dankclaw/compose.yaml. Rootless podman does not restart bare containers, so they did not survive the 2026-09-11 reboot for the amd_iommu=off change and had to be restored by hand — the same gap that had already cost the fleet Caddy and Pi-hole, which is why the boot-persistence rule exists. Each is now a rootless Quadlet unit under dankclaw/observability/quadlet/, installed and dispatched by deploy_observability in deploy-remote.sh, with the compose definitions retired so only one owner exists per container name. Images, mounts, arguments and container names are unchanged, so Caddy's proxies and Prometheus' own scrape config keep resolving. Two posture changes, both about the unauthenticated surface: * The three HTTP UIs published on 0.0.0.0 with no authentication of any kind (Prometheus even with the lifecycle endpoint enabled). They now publish on 127.0.0.1: the browser path is the guarded franksdank.cloud vhost, Caddy reaches them by name over dankclaw_default, and the host-side homepage status timer probes loopback. Nothing legitimately used the LAN port. * node-exporter bound :9100 on every interface; it now binds exactly its two consumers, loopback (homepage timer) and the LAN address (Prometheus, which runs in a container and cannot reach the host's loopback). alloy gains a reachable health endpoint: its UI listens on the container's own loopback and alloy 1.x refuses a config block for it, so the compose service had nothing to probe — the homepage status card for alloy read 'down' continuously. The unit moves the UI with a command-line flag and publishes 127.0.0.1:12345. grafana keeps its Authentik OAuth client secret without receiving the whole .env: systemd expands the value from a [Service] EnvironmentFile, which never reaches the container.
174 lines
8.5 KiB
YAML
174 lines
8.5 KiB
YAML
# Dankclaw stack — AI, search, development & observability services.
|
|
# Deploy: cd /home/dank/serverAppConfigs && podman-compose -f dankclaw/compose.yaml up -d
|
|
# See ../docs/Dankclaw/ and ../docs/Infrastructure/ for service documentation.
|
|
|
|
services:
|
|
# --- SearXNG stack (gluetun + searxng + valkey) --------------------------
|
|
# These three services are now boot-persistent Quadlet units, not compose
|
|
# services. They live in searxng/quadlet/ and install to
|
|
# ~/.config/containers/systemd/ on the host. searxng reuses gluetun's netns
|
|
# (Network=gluetun.container) so its traffic exits the WireGuard tunnel.
|
|
# Deploy with: scripts/deploy-searxng-quadlet.sh dankclaw
|
|
# See ../docs/Dankclaw/SearXNG.md, Gluetun.md, Valkey.md.
|
|
|
|
# --- AI image generation (ROCm GPU backend, gfx1151) ---------------------
|
|
# Built from Containerfile.comfyui on AMD's gfx1151 torch base. The GPU is
|
|
# reached via the /dev/kfd + /dev/dri passthrough (checked host-side; the run
|
|
# user is already in the render group). Only the 'video' group is added:
|
|
# rootless podman cannot map the render gid into the container namespace.
|
|
# Models + data persist under /home/dank/comfyui-data. See ../docs/Dankclaw/ComfyUI.md.
|
|
comfyui:
|
|
build:
|
|
context: .
|
|
dockerfile: Containerfile.comfyui
|
|
image: comfyui-rocm:gfx1151
|
|
container_name: comfyui
|
|
restart: unless-stopped
|
|
command: ["python", "main.py", "--listen", "0.0.0.0", "--port", "8188"]
|
|
ports:
|
|
- "8188:8188"
|
|
devices:
|
|
- /dev/kfd:/dev/kfd
|
|
- /dev/dri:/dev/dri
|
|
# keep-groups preserves the launching user's real supplementary groups;
|
|
# a literal `render` entry fails at container start under rootless podman
|
|
# because the group name cannot be resolved inside the user namespace.
|
|
group_add:
|
|
- keep-groups
|
|
volumes:
|
|
- /home/dank/comfyui-data/models:/opt/ComfyUI/models
|
|
- /home/dank/comfyui-data/input:/opt/ComfyUI/input
|
|
- /home/dank/comfyui-data/output:/opt/ComfyUI/output
|
|
- /home/dank/comfyui-data/custom_nodes:/opt/ComfyUI/custom_nodes
|
|
healthcheck:
|
|
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8188/')"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 180s
|
|
|
|
# --- Chat UI (open-webui) ------------------------------------------------
|
|
# Now a boot-persistent Quadlet unit (quadlet, slirp4netns, port 3005), not
|
|
# a compose service. See open-webui/quadlet/open-webui.container and
|
|
# ../docs/Dankclaw/OpenWebUI.md.
|
|
|
|
hermes:
|
|
image: docker.io/nousresearch/hermes-agent:latest
|
|
container_name: hermes
|
|
network_mode: host
|
|
restart: unless-stopped
|
|
# The image's default s6 entrypoint runs the interactive TUI, which exits
|
|
# without a TTY and crash-loops the container. Override it to run the
|
|
# headless gateway (the documented Docker mode). The gateway drops to the
|
|
# unprivileged hermes user (uid 10000), so the data dir must be writable by it.
|
|
entrypoint: ["bash", "-c", "hermes gateway run --force -v"]
|
|
environment:
|
|
HERMES_DATA_DIR: /data
|
|
# Point hermes's config/.env at the mounted data dir (persistent).
|
|
HERMES_HOME: /data
|
|
# Signal adapter -> the signal-cli daemon, a HOST process listening on
|
|
# :8080. The service runs with network_mode: host below, so it dials
|
|
# loopback directly; from a bridge network only DNAT'd published ports
|
|
# accept connections to the host LAN IP, and un-forwarded ports refuse
|
|
# instantly (verified live 2026-08-30).
|
|
SIGNAL_HTTP_URL: "http://127.0.0.1:8080"
|
|
SIGNAL_ACCOUNT: "${SIGNAL_ACCOUNT}"
|
|
SIGNAL_ALLOWED_USERS: "${SIGNAL_ALLOWED_USERS}"
|
|
ports:
|
|
- "8090:8090"
|
|
volumes:
|
|
- /home/dank/hermes-data:/data
|
|
# No healthcheck: 'hermes gateway run' starts the messaging gateway (Signal
|
|
# SSE), not the :8090 web dashboard, so there is no HTTP endpoint to probe.
|
|
# The container's running state + the daemon's SSE connection are the signal.
|
|
|
|
# --- Hermes web dashboard (management UI: config, API keys, sessions) -----
|
|
# A separate process from the Signal gateway (the gateway entrypoint does not
|
|
# start the dashboard). Bound to the LAN with basic auth: the username + a
|
|
# scrypt password_hash live in the git-ignored /home/dank/hermes-data config.
|
|
# Port 9119 is published so a browser on the LAN can reach it.
|
|
hermes-dashboard:
|
|
image: docker.io/nousresearch/hermes-agent:latest
|
|
container_name: hermes-dashboard
|
|
restart: unless-stopped
|
|
entrypoint: ["bash", "-c", "hermes dashboard --host 0.0.0.0 --port 9119 --no-open --skip-build"]
|
|
environment:
|
|
HERMES_DATA_DIR: /data
|
|
HERMES_HOME: /data
|
|
ports:
|
|
- "9119:9119"
|
|
volumes:
|
|
- /home/dank/hermes-data:/data
|
|
|
|
# --- Hermes web UI (Hermex server; phone-facing API for the agent) --------
|
|
# Runs the open-source hermes-webui (github.com/nesquena/hermes-webui) in
|
|
# foreground mode: bootstrap.py creates .venv inside the mounted checkout,
|
|
# installs WebUI + agent deps, then execs server.py as a single foreground
|
|
# process. It reads HERMES_HOME=/data, so it drives the SAME agent state
|
|
# (memory, skills, sessions) as the gateway container.
|
|
# Loopback-only publish: the SSH tunnel (phone -> Dankclaw:22) is the sole
|
|
# ingress. The password gates Hermex logins and lives in the host .env.
|
|
hermes-webui:
|
|
image: docker.io/nousresearch/hermes-agent:latest
|
|
container_name: hermes-webui
|
|
restart: unless-stopped
|
|
entrypoint: ["bash", "-c", "python3 /webui/bootstrap.py --foreground"]
|
|
environment:
|
|
HERMES_HOME: /data
|
|
# Container-internal bind: 0.0.0.0 so Podman's port forwarder (which
|
|
# dials the container's bridge IP) can reach it. The *host-side*
|
|
# publish below stays on 127.0.0.1, so loopback-only ingress holds.
|
|
HERMES_WEBUI_HOST: 0.0.0.0
|
|
HERMES_WEBUI_PORT: "8787"
|
|
HERMES_WEBUI_PASSWORD: "${HERMES_WEBUI_PASSWORD}"
|
|
# Headless host: never show the first-run wizard (a profile already
|
|
# exists in /data from the gateway).
|
|
HERMES_WEBUI_SKIP_ONBOARDING: "1"
|
|
ports:
|
|
- "127.0.0.1:8787:8787"
|
|
volumes:
|
|
- /home/dank/hermes-data:/data
|
|
# rw on purpose: bootstrap.py creates /webui/.venv in the repo root.
|
|
- /home/dank/hermes-webui:/webui
|
|
healthcheck:
|
|
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8787/health')"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
# First boot pip-installs WebUI + agent deps into a fresh venv.
|
|
start_period: 600s
|
|
|
|
# --- Git hosting (Gitea) -------------------------------------------------
|
|
# Now boot-persistent Quadlet units (private bridge network, DNS enabled),
|
|
# not compose services. See gitea/quadlet/ and ../docs/Dankclaw/Gitea.md.
|
|
|
|
# --- Observability -------------------------------------------------------
|
|
# Now boot-persistent Quadlet units, not compose services: prometheus,
|
|
# grafana, loki, alloy and node-exporter install from
|
|
# dankclaw/observability/quadlet/ and are dispatched by deploy_observability
|
|
# in scripts/deploy-remote.sh. They left compose on 2026-09-11, after a host
|
|
# reboot proved that compose-only containers do not come back on their own —
|
|
# the same gap that had already cost the fleet Caddy and Pi-hole.
|
|
#
|
|
# The three HTTP UIs publish on 127.0.0.1 only. Off-host access is through
|
|
# the guarded franksdank.cloud vhosts (Caddy reaches these containers by
|
|
# name over this network) and the host's own homepage status timer probes
|
|
# them over loopback. See
|
|
# serverAppConfigs-docs/Infrastructure/Observability.md.
|
|
|
|
# --- Authentik (SSO / OIDC provider) -------------------------------------
|
|
# Now boot-persistent Quadlet units, not compose services: authentik-
|
|
# postgres -> authentik-server -> authentik-worker start in dependency order
|
|
# via ~/.config/containers/systemd/*.container on the host (repo source:
|
|
# dankclaw/authentik/quadlet/; deploy-remote.sh deploy_authentik installs
|
|
# and starts them). Redis was removed in 2025.10 (sessions/caching moved to
|
|
# PostgreSQL). The Caddyfile's forward_auth vhosts proxy to
|
|
# authentik-server:9000 over the dankclaw_default network (A2A DNS).
|
|
# See ../docs/Infrastructure/Authentik.md.
|
|
|
|
# Caddy (the homelab's single reverse proxy) left compose on 2026-08-26:
|
|
# it is boot-persistent via its Quadlet unit (dankclaw/caddy/quadlet/
|
|
# caddy.container), installed by deploy_caddy in scripts/deploy-remote.sh.
|
|
# The rendered Caddyfile, certs, and config still live under
|
|
# /home/dank/Containers/caddy (rootless-writable).
|