host: piha services: ha-diag-agent: role: ha-diagnostic-agent deployment_model: docker-compose exposure: local-only offline_required: false depends_on: local: [] external: [homeassistant] config: target_url: http://localhost:8123 location_tag: "ken" events_dir: /opt/homelab/events/piha runtime: config_path: /opt/homelab/config/ha-diag-agent data_path: /var/lib/ha-diag-agent node-agent: role: node-stability-monitor deployment_model: docker-compose exposure: local-only offline_required: true depends_on: local: [] external: [] runtime: config_path: /opt/homelab/config/node-agent data_path: /opt/homelab/state logs_path: /opt/homelab/events brain-watchdog: role: control-plane-watchdog deployment_model: docker-compose exposure: private offline_required: false depends_on: local: [] external: [control-plane] runtime: config_path: /opt/homelab/config/brain-watchdog vikunja: role: task-management deployment_model: docker-compose exposure: private # public via npm reverse proxy at vikunja.okit.pl offline_required: false depends_on: local: [] external: [forgejo] # OIDC identity provider ports: - name: http container_port: 3456 protocol: tcp runtime: # secrets (.env) + OIDC config (config.yml) live alongside the compose file config_path: services/vikunja # data is in Docker named volumes: vikunja_vikunja_db, vikunja_vikunja_files llm-gateway: role: llm-router # FastAPI proxy -> Ollama @ SOLARIA :11434 deployment_model: docker-compose exposure: private # Tailscale-only bind (TAILSCALE_BIND_IP); no public ingress offline_required: false depends_on: local: [] external: [ollama] # SOLARIA may be powered down -> routes 502, health stays ok ports: - name: http container_port: 8080 protocol: tcp runtime: # .env (TAILSCALE_BIND_IP) lives alongside the compose file; stateless, no data path config_path: services/llm-gateway node_exporter: role: metrics-exporter deployment_model: docker-compose exposure: local-only offline_required: true depends_on: local: [] external: [] runtime: # textfile collector reads /opt/homelab/state/node-exporter (module 5 phase 3 step 5, # kb/phases/kb-m5-faza3.md §7.2 — kb-ingest.prom) via the existing /:/host:ro # mount, see hosts/piha/runtime/node_exporter/docker-compose.override.yml. data_path: /opt/homelab/state/node-exporter kb-postgres: role: kb-database # KB spine: Postgres 16 + pgvector (always-on) deployment_model: docker-compose exposure: local-only # Tailscale-accessible; no public ingress offline_required: false depends_on: local: [] external: [] ports: - name: postgres host_port: 5433 protocol: tcp runtime: config_path: /opt/homelab/config/kb-postgres # data is in Docker named volume kb_postgres_data — must land on the NVMe # (Docker data-root on /home); see hosts/piha/runtime/kb-postgres override. ollama-piha: role: embed-fallback # module 5 phase 4 (plan §2 D2/§5): local CPU bge-m3 for kb-query while SOLARIA sleeps deployment_model: docker-compose exposure: private # 127.0.0.1 + LAN bind (LAN_BIND_IP) only; no public/Tailscale exposure offline_required: false depends_on: local: [] external: [] ports: - name: http container_port: 11434 host_port: 11434 protocol: tcp runtime: # .env (LAN_BIND_IP) lives alongside the compose file; models live in the # Docker named volume ollama_piha_models (NVMe data-root, like kb-postgres). # Deploy note: `docker exec ollama-piha ollama pull bge-m3` is a manual # post-deploy step — the image ships no models. config_path: services/ollama-piha kb-query: role: kb-search-api # module 5 phase 4: FastAPI wrapper over kb_retrieval cascade/flat query deployment_model: docker-compose exposure: private # LAN bind (LAN_BIND_IP); npm@PIHA vhost + OIDC is a later step offline_required: false depends_on: # ollama-piha is a soft local dependency: the embed fallback while SOLARIA # sleeps — kb-query starts and serves /healthz without it, /search degrades # to 503 only when BOTH embed backends are unreachable. local: [kb-postgres, ollama-piha] external: [ollama] # primary embed backend @ SOLARIA; may be offline -> fallback to ollama-piha ports: - name: http container_port: 8080 host_port: 8230 protocol: tcp runtime: # .env (KB_DSN, LAN_BIND_IP) lives alongside the compose file; stateless, no data path config_path: services/kb-query narty27: role: static-html-host # single self-contained viz.html (narty 2027 / Saalbach KB export) deployment_model: docker-compose exposure: private # LAN only; no npm vhost, no public ingress offline_required: false depends_on: local: [] external: [] ports: - name: http container_port: 80 host_port: 8240 protocol: tcp runtime: # No config and no secrets. Content is PERSONAL and deliberately outside # the repo — it lives only in the Docker named volume # narty27_narty27_content, refreshed from SOLARIA (kb/runbooks/narty27-deploy.md). # No backup job, no /opt/homelab/data bind. config_path: services/narty27 kb-site: role: static-html-host # public KB slice generated by scripts/kb/gen_pages.py deployment_model: docker-compose exposure: public # via npm@PIHA vhost kb.okit.pl; container binds LAN :8250 offline_required: false depends_on: local: [] external: [] ports: - name: http container_port: 80 host_port: 8250 protocol: tcp runtime: # No config and no secrets. Content is a pure artifact of the repo: it # lives only in the Docker named volume kb-site_kb-site_content and is # refreshed by regenerating from kb/ (kb/runbooks/kb-site-deploy.md). # No backup job, no /opt/homelab/data bind. config_path: services/kb-site # --- Known unmanaged containers on piha (recon B5/B6, 2026-07-27) ---------- # ~28 running containers have no entry above and are deliberately NOT being # added piecemeal — bringing them under desired state is a later stage # (recon open question 5). The set: agent-system ×4 (webui, # runtime-materializer, telegram-bot, redis — these STAY, decision # 2026-07-28), stability-agent, paperless ×3, immich ×4, forgejo ×3, # zigbee2mqtt, audiobookshelf, grafana, wikijs ×2, code-server, portainer, # prometheus, homepage, actual-budget, mqtt-exporter, owntracks ×3, # vaultwarden, pihole-exporter (unlabelled), fail2ban-exporter, # nginxproxymanager — plus the HOST systemd mosquitto (OS package, no repo # definition at all, recon C8). When adding any of these later, match the # entry key to the world-state service key (supervisor does exact # / matching) or it will drift as missing_service.