comfyui: replace the stale compose container with a Quadlet unit
ComfyUI had failed 9,146 times and had been down ~7 hours when this was found:
its container was created 2026-09-15 14:12, BEFORE the GPU declarations were
added to the compose service, and `restart: unless-stopped` kept restarting
that stale container — no /dev/kfd, no /dev/dri, no keep-groups — so it died
every time on `RuntimeError: No HIP GPUs are available`. Its "unit" was a
hand-written ~/.config/systemd/user/comfyui.service that shelled out to
`podman compose up -d`, which is not repo-tracked state and collides by name
with the Quadlet the generator produces.
- dankclaw/comfyui/quadlet/comfyui.container: image comfyui-rocm:gfx1151
(built from Containerfile.comfyui), /dev/kfd + /dev/dri, keep-groups,
the four comfyui-data volumes, loopback-only publish (an unguarded service
is internal-only here; the homepage card probes 127.0.0.1:8188), Restart=
always, WantedBy=default.target.
- the compose service is deleted — one owner per container name.
- deploy_comfyui builds the image, installs the unit, retires the compose-era
unit into ~/.AGENTTRASH, removes the stale container so the unit's own can
take the name, and waits on :8188.
- the healthcheck is a file, because the podman 5.x v1 generator splits
HealthCmd on spaces and an inline `python3 -c "..."` becomes a quoting
puzzle that fails silently.
The comfyui seam now reads the unit instead of compose for devices, command and
groups (and resolves the Containerfile from the deployer's declaration, not
from a directory listing).
This commit is contained in:
1 parent
d8835e69c6
commit
03d44d3308
5 files changed
+180
-104
No files matched your search
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ComfyUI container healthcheck: the UI must answer on :8188.
|
||||
|
||||
Runs INSIDE the container, where the UI listens (the unit publishes that port on
|
||||
the host loopback for the homepage status card). ComfyUI only answers once
|
||||
torch, the custom nodes and the graph are loaded, so a 200 here means the
|
||||
service is usable rather than merely a live process — the distinction that
|
||||
matters, because the failure this replaced was a container that stayed "up"
|
||||
while dying on every start.
|
||||
|
||||
HealthCmd in the quadlet splits on spaces into an argv array (podman 5.x v1
|
||||
generator), which is why this is a file and not an inline `python3 -c`.
|
||||
"""
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen("http://127.0.0.1:8188/", timeout=10) as resp:
|
||||
sys.exit(0 if resp.status == 200 else 1)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,68 @@
|
||||
# Quadlet unit: ComfyUI (rootless, boot-persistent).
|
||||
#
|
||||
# Install path on dankclaw: ~/.config/containers/systemd/comfyui.container
|
||||
# Unit name: comfyui.service (container name: comfyui)
|
||||
#
|
||||
# WHY THIS FILE EXISTS. ComfyUI ran from `dankclaw/compose.yaml` through a
|
||||
# hand-written `~/.config/systemd/user/comfyui.service` that called
|
||||
# `podman compose up -d`. Its container was created 2026-09-15 14:12, BEFORE the
|
||||
# GPU declarations were added to the compose service, and `restart:
|
||||
# unless-stopped` then kept restarting that stale container — so it started
|
||||
# without /dev/kfd, without /dev/dri and without keep-groups, died on
|
||||
# `RuntimeError: No HIP GPUs are available`, and had restarted 9,146 times when
|
||||
# this was found. A recreate via compose would have fixed the container; a unit
|
||||
# fixes the class: one owner per container name, repo-tracked, boot-persistent
|
||||
# through the generator.
|
||||
#
|
||||
# GPU ACCESS. /dev/kfd + /dev/dri plus keep-groups. A literal `render` group
|
||||
# entry cannot work under rootless Podman (the group name does not resolve
|
||||
# inside the user namespace), and the companion udev rule
|
||||
# (dankclaw/udev/99-comfyui-gpu-access.rules) keeps those nodes MODE 0666 so a
|
||||
# user-unit spawn — which carries no supplementary groups — can still open them.
|
||||
# Both halves are asserted by tests/comfyui_rocm.bats.
|
||||
#
|
||||
# EXPOSURE. Loopback only. ComfyUI has no authentication and executes arbitrary
|
||||
# node graphs, and it has no Caddy vhost, so the repository's rule applies:
|
||||
# an unguarded service is internal-only. The homepage status card probes
|
||||
# 127.0.0.1:8188, which this satisfies. To reach the UI from another machine,
|
||||
# guard it behind the auth chain and give it a vhost — do not widen the bind.
|
||||
#
|
||||
# DATA. Models, input, output and custom_nodes live under
|
||||
# /home/dank/comfyui-data (host state, not in the repo).
|
||||
[Unit]
|
||||
Description=ComfyUI (gfx1151 ROCm image generation)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
ContainerName=comfyui
|
||||
Image=localhost/comfyui-rocm:gfx1151
|
||||
Pull=never
|
||||
Network=dankclaw_default
|
||||
PublishPort=127.0.0.1:8188:8188
|
||||
AddDevice=/dev/kfd
|
||||
AddDevice=/dev/dri
|
||||
GroupAdd=keep-groups
|
||||
Volume=/home/dank/comfyui-data/models:/opt/ComfyUI/models
|
||||
Volume=/home/dank/comfyui-data/input:/opt/ComfyUI/input
|
||||
Volume=/home/dank/comfyui-data/output:/opt/ComfyUI/output
|
||||
Volume=/home/dank/comfyui-data/custom_nodes:/opt/ComfyUI/custom_nodes
|
||||
Volume=/home/dank/serverAppConfigs/dankclaw/comfyui/comfyui-healthcheck.py:/usr/local/bin/comfyui-healthcheck.py:ro
|
||||
Exec=python main.py --listen 0.0.0.0 --port 8188
|
||||
# The probe lives in a file because the podman 5.x v1 generator splits
|
||||
# HealthCmd on spaces into an argv array; an inline `python3 -c "..."` becomes
|
||||
# a puzzle of quoting that fails silently.
|
||||
HealthCmd=python3 /usr/local/bin/comfyui-healthcheck.py
|
||||
HealthInterval=30s
|
||||
HealthTimeout=10s
|
||||
HealthRetries=3
|
||||
# First start loads torch and the node graph; give it room before the probe
|
||||
# counts against it.
|
||||
HealthStartPeriod=300s
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
+8
-34
@@ -12,40 +12,14 @@ services:
|
||||
# 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
|
||||
# ComfyUI is a boot-persistent Quadlet unit since 2026-09-16:
|
||||
# comfyui/quadlet/comfyui.container, installed by deploy-remote.sh. It left
|
||||
# compose after the container it had started went stale — created before the
|
||||
# GPU declarations existed, then restarted by `restart: unless-stopped`
|
||||
# without /dev/kfd or /dev/dri until it had failed 9,146 times on
|
||||
# "No HIP GPUs are available". Built from Containerfile.comfyui (AMD's
|
||||
# gfx1151 base), models and data under /home/dank/comfyui-data.
|
||||
# See ../docs/Dankclaw/ComfyUI.md.
|
||||
|
||||
# --- Chat UI (open-webui) ------------------------------------------------
|
||||
# Now a boot-persistent Quadlet unit (quadlet, slirp4netns, port 3005), not
|
||||
|
||||
Reference in new issue
Block a user