Rootless podman cannot map the 'render' gid (990) into the container namespace (dank's subgid range is 100000+), so --group-add render fails to start the container. GPU access actually comes from the /dev/kfd + /dev/dri passthrough, checked host-side (the run user is already in 'render' on the host). So add only the 'video' group (which resolves) and drop 'render'. Update the group test to assert video present and render ABSENT (with the rootless rationale), per the approved rootless config.
248 lines
7.4 KiB
YAML
248 lines
7.4 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 /opt/comfyui. 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
|
|
group_add:
|
|
- video
|
|
volumes:
|
|
- /opt/comfyui/models:/opt/ComfyUI/models
|
|
- /opt/comfyui/input:/opt/ComfyUI/input
|
|
- /opt/comfyui/output:/opt/ComfyUI/output
|
|
- /opt/comfyui/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 + agent -----------------------------------------------------
|
|
openwebui:
|
|
image: ghcr.io/open-webui/open-webui:main
|
|
container_name: openwebui
|
|
restart: unless-stopped
|
|
environment:
|
|
OPENAI_API_BASE_URL: "${OPENAI_API_BASE_URL}"
|
|
OPENAI_API_KEY: "${OPENAI_API_KEY}"
|
|
ports:
|
|
- "3000:8080"
|
|
volumes:
|
|
- /opt/openwebui/data:/app/backend/data
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
hermes:
|
|
image: docker.io/nousresearch/hermes-agent:latest
|
|
container_name: hermes
|
|
restart: unless-stopped
|
|
environment:
|
|
HERMES_DATA_DIR: /data
|
|
ports:
|
|
- "8090:8090"
|
|
volumes:
|
|
- /opt/hermes/data:/data
|
|
healthcheck:
|
|
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8090/')"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
# --- Git hosting (migrated from /opt/gitea) ------------------------------
|
|
postgres:
|
|
image: docker.io/library/postgres:16-alpine
|
|
container_name: gitea-postgres
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: gitea
|
|
POSTGRES_PASSWORD: "${GITEA_DB_PASSWORD}"
|
|
POSTGRES_DB: gitea
|
|
volumes:
|
|
- /opt/gitea/data/gitea-postgres:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U gitea"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
gitea:
|
|
image: docker.io/gitea/gitea:latest
|
|
container_name: gitea-server
|
|
restart: unless-stopped
|
|
environment:
|
|
DATABASE_TYPE: postgres
|
|
DATABASE_HOST: gitea-postgres
|
|
DATABASE_NAME: gitea
|
|
DATABASE_USER: gitea
|
|
DATABASE_PASSWD: "${GITEA_DB_PASSWORD}"
|
|
DATABASE_PORT: "5432"
|
|
USER_UID: "1000"
|
|
USER_GID: "1000"
|
|
ports:
|
|
- "3001:3000"
|
|
- "2222:22"
|
|
volumes:
|
|
- /opt/gitea/data/gitea-gitea:/data
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
depends_on:
|
|
- postgres
|
|
healthcheck:
|
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
# --- Observability -------------------------------------------------------
|
|
prometheus:
|
|
image: docker.io/prom/prometheus:latest
|
|
container_name: prometheus
|
|
restart: unless-stopped
|
|
command:
|
|
- --config.file=/etc/prometheus/prometheus.yml
|
|
- --storage.tsdb.path=/prometheus
|
|
- --storage.tsdb.retention.time=30d
|
|
- --web.enable-lifecycle
|
|
ports:
|
|
- "9090:9090"
|
|
volumes:
|
|
- /opt/prometheus/config:/etc/prometheus
|
|
- /opt/prometheus/data:/prometheus
|
|
healthcheck:
|
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9090/-/healthy"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
grafana:
|
|
image: docker.io/grafana/grafana:latest
|
|
container_name: grafana
|
|
restart: unless-stopped
|
|
environment:
|
|
GF_SERVER_ROOT_URL: "http://grafana.dankclaw.local"
|
|
GF_SECURITY_ADMIN_USER: "${GRAFANA_ADMIN_USER}"
|
|
GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_ADMIN_PASSWORD}"
|
|
ports:
|
|
- "3002:3000"
|
|
volumes:
|
|
- /opt/grafana/data:/var/lib/grafana
|
|
- /opt/grafana/provisioning:/etc/grafana/provisioning:ro
|
|
depends_on:
|
|
- prometheus
|
|
healthcheck:
|
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
loki:
|
|
image: docker.io/grafana/loki:latest
|
|
container_name: loki
|
|
restart: unless-stopped
|
|
command: ["-config.file=/etc/loki/loki-config.yaml"]
|
|
ports:
|
|
- "3100:3100"
|
|
volumes:
|
|
- /opt/loki/config:/etc/loki
|
|
- /opt/loki/data:/loki
|
|
healthcheck:
|
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3100/ready"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
alloy:
|
|
image: docker.io/grafana/alloy:latest
|
|
container_name: alloy
|
|
restart: unless-stopped
|
|
volumes:
|
|
- /opt/alloy/config:/etc/alloy
|
|
- /run/systemd/journal/socket:/run/systemd/journal/socket:ro
|
|
- /var/log:/var/log:ro
|
|
- /var/lib/containers:/var/lib/containers:ro
|
|
depends_on:
|
|
- loki
|
|
healthcheck:
|
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:12345/-/healthy"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
node-exporter:
|
|
image: docker.io/prom/node-exporter:latest
|
|
container_name: node-exporter
|
|
restart: unless-stopped
|
|
network_mode: host
|
|
pid: host
|
|
volumes:
|
|
- /proc:/host/proc:ro
|
|
- /sys:/host/sys:ro
|
|
- /:/rootfs:ro
|
|
command:
|
|
- --path.procfs=/host/proc
|
|
- --path.sysfs=/host/sys
|
|
- --path.rootfs=/rootfs
|
|
healthcheck:
|
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9100/metrics"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
# --- Reverse proxy + homepage -------------------------------------------
|
|
caddy:
|
|
image: docker.io/library/caddy:latest
|
|
container_name: caddy
|
|
restart: unless-stopped
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- /opt/caddy/data:/data
|
|
- /opt/caddy/config:/config
|
|
- ../shared/homepage:/srv/homepage:ro
|
|
depends_on:
|
|
- searxng
|
|
- comfyui
|
|
- openwebui
|
|
- hermes
|
|
- gitea
|
|
- grafana
|
|
- prometheus
|
|
- loki
|
|
healthcheck:
|
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:80/"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|