comfyui-mcp 0.41.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/orchestrator/gemini-backend.js +109 -29
  2. package/dist/orchestrator/gemini-backend.js.map +1 -1
  3. package/dist/orchestrator/index.js +106 -5
  4. package/dist/orchestrator/index.js.map +1 -1
  5. package/dist/orchestrator/panel-agent.js +12 -1
  6. package/dist/orchestrator/panel-agent.js.map +1 -1
  7. package/dist/services/ai-toolkit.js +220 -0
  8. package/dist/services/ai-toolkit.js.map +1 -0
  9. package/dist/services/training-config.js +138 -0
  10. package/dist/services/training-config.js.map +1 -0
  11. package/dist/services/training-jobs.js +654 -0
  12. package/dist/services/training-jobs.js.map +1 -0
  13. package/dist/services/workflow-validator.js +4 -0
  14. package/dist/services/workflow-validator.js.map +1 -1
  15. package/dist/tools/diagnostics.js +120 -1
  16. package/dist/tools/diagnostics.js.map +1 -1
  17. package/dist/tools/index.js +63 -3
  18. package/dist/tools/index.js.map +1 -1
  19. package/dist/tools/train.js +187 -0
  20. package/dist/tools/train.js.map +1 -0
  21. package/docker/runpod/.gitattributes +8 -0
  22. package/docker/runpod/Dockerfile +486 -0
  23. package/docker/runpod/README.md +658 -0
  24. package/docker/runpod/config.ini +28 -0
  25. package/docker/runpod/deploy-dockerhub.sh +46 -0
  26. package/docker/runpod/extra_model_paths.yaml +61 -0
  27. package/docker/runpod/nginx.conf +124 -0
  28. package/docker/runpod/post_start.sh +619 -0
  29. package/docker/runpod/starting.html +47 -0
  30. package/docker/runpod/test_image.sh +180 -0
  31. package/docker/runpod/verify_image_remote.py +126 -0
  32. package/docker/trainer/Dockerfile +69 -0
  33. package/package.json +2 -1
  34. package/plugin/skills/train-character-lora/SKILL.md +83 -0
@@ -0,0 +1,658 @@
1
+ # comfyui-mcp RunPod image — FAST RESTART (image-immutable software)
2
+
3
+ A RunPod image that boots **ready to be driven by the
4
+ [comfyui-mcp](https://github.com/artokun/comfyui-mcp) Agent Panel**. Deploy on
5
+ RunPod, run **one local command** on your laptop, and the agent drives the pod's
6
+ live ComfyUI graph from your Claude/ChatGPT subscription.
7
+
8
+ It is optimized for **fast stop/start**. All the software — ComfyUI + its venv +
9
+ `comfyui_manager` v2 + the Agent Panel custom node — is **baked into the
10
+ (immutable) image** at `/opt/ComfyUI` and **run directly from there**. The
11
+ `/workspace` network volume holds **only your data** (`user/`, `models/`,
12
+ `input/`, `output/`). A warm restart does **no install, no sync, no seed** — it
13
+ just `mkdir -p` the data dirs and launches ComfyUI (~**30-60s** ComfyUI init).
14
+
15
+ > **Testing:** every build ends in an in-Dockerfile integrity gate, and
16
+ > [`test_image.sh`](#test-before-you-push-post-build-gate) runs a full boot
17
+ > suite (fresh volume → redeploy → corruption self-heal) before any push;
18
+ > `deploy-dockerhub.sh` makes that gate mandatory for the official image.
19
+
20
+ ---
21
+
22
+ ## TL;DR
23
+
24
+ 1. **Build & push** the image (a machine with Docker; no GPU needed).
25
+ See [Build & push](#build--push).
26
+ 2. **Deploy** on RunPod: GPU pod (e.g. RTX 5090), **expose HTTP 3000**, attach a
27
+ **network volume at `/workspace`**. See [Deploy on RunPod](#deploy-on-runpod).
28
+ 3. On your laptop:
29
+ ```bash
30
+ npx -y comfyui-mcp@latest connect https://<pod-id>-3000.proxy.runpod.net
31
+ ```
32
+ 4. Open the pod's ComfyUI, open the **Agent Panel** sidebar, enable the
33
+ external-orchestrator toggle, hit **Connect**, and drive the graph in natural
34
+ language.
35
+
36
+ The agent's brain (the **panel orchestrator**) runs **locally on your machine**
37
+ on your own subscription — see [Topology](#topology-where-the-agent-runs).
38
+
39
+ ---
40
+
41
+ ## The fast-restart model (what changed and why)
42
+
43
+ The previous design **seeded/synced** ComfyUI + its venv onto `/workspace` on the
44
+ first boot and re-synced on upgrades. That made the volume the source of truth for
45
+ software, which is slow to sync, fragile to upgrade (the `rsync -u` venv caveat),
46
+ and couples software to the data volume.
47
+
48
+ This design **inverts** that:
49
+
50
+ | Concern | Where it lives | Persists a restart? |
51
+ |---------|----------------|---------------------|
52
+ | ComfyUI install + venv | **image** `/opt/ComfyUI` (immutable) | n/a (re-pulled with the image) |
53
+ | `custom_nodes` (Agent Panel + your installs) | **volume** `/workspace/custom_nodes` (symlinked; image nodes seeded each boot) | **Yes** |
54
+ | ComfyUI-Manager | **image** venv (pip package) | n/a (in the image) |
55
+ | Custom-node pip cache | **volume** `/workspace/.cache/pip` | **Yes** |
56
+ | Other caches (HF / torch / npm) | **container** ephemeral disk | **No** — ephemeral |
57
+ | `user/` (workflows + settings + Manager config) | **volume** `/workspace/user` | **Yes** |
58
+ | `models/` (incl. Manager downloads) | **volume** `/workspace/models` | **Yes** |
59
+ | `input/` | **volume** `/workspace/input` | **Yes** |
60
+ | `output/` | **volume** `/workspace/output` | **Yes** |
61
+
62
+ **Result:** the base ComfyUI + venv boot fast from the immutable image; only your
63
+ data **and your custom nodes** live on the volume. A restart is `mkdir -p` the
64
+ user-data dirs, symlink + seed `custom_nodes`, reinstall node deps from the
65
+ persistent cache, and launch from the baked venv. No full install/sync/seed.
66
+
67
+ ### Custom nodes persist on the volume
68
+
69
+ `custom_nodes` is **symlinked to `/workspace/custom_nodes`**, so **custom nodes the
70
+ agent or Manager install at runtime survive a restart** — while the base ComfyUI +
71
+ venv still boot fast from the immutable image.
72
+
73
+ * **How it works (each boot).** The entrypoint symlinks
74
+ `/opt/ComfyUI/custom_nodes` → `/workspace/custom_nodes`, seeds/refreshes the
75
+ image's baked nodes (Agent Panel + ComfyUI builtins) into it — so an image
76
+ upgrade always ships a current panel while **your** installed nodes are preserved
77
+ — then reinstalls each node's `requirements.txt` into the venv from a
78
+ **persistent pip cache** on the volume (fast after the first time).
79
+ * **Models DO persist** — Manager's install-model writes to `/workspace/models`
80
+ (see [Model paths](#model-paths-extra_model_pathsyaml)).
81
+ * **First install of a node** downloads its Python deps into the persistent cache;
82
+ every restart after re-materializes them from cache (the venv lives in the image,
83
+ so its packages are refreshed on boot). Compiled/CUDA-linked deps may rebuild
84
+ rather than unpack. To bake a node so it needs **zero** boot-time work, still add
85
+ it to the `Dockerfile` and rebuild.
86
+
87
+ ### If the panel (or a node) shows up EMPTY — 0-byte files
88
+
89
+ A **full network volume** is the classic cause: on ENOSPC, `cp`/`git` still
90
+ *create* every file but write 0 bytes into them, so the node's whole file tree
91
+ exists as empty husks — ComfyUI lists the node, but nothing loads. Because the
92
+ volume persists, this used to survive every redeploy.
93
+
94
+ The entrypoint now defends itself:
95
+
96
+ * logs the volume's **free space** at boot and prints a loud warning under 500 MB;
97
+ * **skips the ~7 GB spotcheck-model copy** when the volume can't hold it;
98
+ * **verifies the Agent Panel** on the volume every boot and **self-heals** a
99
+ broken copy (re-clone from GitHub, or restore from the image seed offline);
100
+ * names any other custom node whose `__init__.py` is 0 bytes in the boot log
101
+ (`WARN: custom nodes with 0-byte __init__.py …`) — reinstall those via Manager.
102
+
103
+ If you see the ENOSPC warning: free or grow the volume, restart the pod, and the
104
+ panel repairs itself on the next boot.
105
+
106
+ ---
107
+
108
+ ## Architecture (multi-stage)
109
+
110
+ ```
111
+ ARG BASE_IMAGE = runpod/pytorch:1.0.7-cu1281-torch280-ubuntu2404 (lean, recent)
112
+ ARG RUNPOD_SRC_IMAGE = aitrepreneur/comfyui:2.3.5 (donor only)
113
+
114
+ ┌─ STAGE A: runpod-donor ── ARG-gated (INCLUDE_RUNPOD_EXTRAS) ─────────┐
115
+ │ FROM aitrepreneur/comfyui:2.3.5 │
116
+ │ (built only so runpod-extras-1 can COPY service artifacts out of it;│
117
+ │ INCLUDE_RUNPOD_EXTRAS=0 -> runpod-extras-0 (empty), and BuildKit │
118
+ │ never builds/pulls this stage at all) │
119
+ └─────────────────────────────────────────────────────────────────────┘
120
+ ┌─ spotcheck-0 / spotcheck-1 (alpine) ─ ARG-gated SDXL model carrier ──┐
121
+ │ spotcheck-1 = ADD sd_xl_base_1.0.safetensors ; spotcheck-0 = empty │
122
+ └─────────────────────────────────────────────────────────────────────┘
123
+ ┌─ FINAL: FROM ${BASE_IMAGE} ─────────────────────────────────────────┐
124
+ │ apt: git cron nodejs … ; install code-server │
125
+ │ git clone ComfyUI (master) -> /opt/ComfyUI (BAKED, run from here) │
126
+ │ python -m venv /opt/ComfyUI/venv │
127
+ │ pip cu128 torch/vision/audio (NO xformers) │
128
+ │ pip -r requirements.txt ; pip comfyui_manager==4.2.2 │
129
+ │ rm classic custom_nodes/ComfyUI-Manager │
130
+ │ git clone comfyui-mcp-panel -> /opt/ComfyUI/custom_nodes/… │
131
+ │ COPY extra_model_paths.yaml -> /opt/ComfyUI/extra_model_paths.yaml │
132
+ │ COPY config.ini -> /opt/ComfyUI/config.ini.seed (Manager gate) │
133
+ │ COPY --from=spotcheck-src -> /opt/ComfyUI-seed-models │
134
+ │ COPY --from=runpod-extras /extras/ / (uploader, croc, app-manager)│
135
+ │ COPY nginx.conf (:3000->:3001), starting.html, post_start.sh │
136
+ │ (no ENTRYPOINT/CMD override — keep the base's /start.sh chain) │
137
+ └─────────────────────────────────────────────────────────────────────┘
138
+ ```
139
+
140
+ ### Boot sequence (what runs, in order)
141
+
142
+ The base image's `CMD` is **`/start.sh`** (from `runpod/containers`). On boot it:
143
+
144
+ 1. `service nginx start` — reads our `/etc/nginx/nginx.conf` (**:3000 → :3001**,
145
+ websocket-aware, with the "starting" fallback page).
146
+ 2. runs `/pre_start.sh` if present (we ship none).
147
+ 3. `setup_ssh` — injects RunPod's `$PUBLIC_KEY` and starts sshd.
148
+ 4. `start_jupyter` — starts JupyterLab on **:8888** if `$JUPYTER_PASSWORD` is set.
149
+ 5. runs **our `/post_start.sh`** — `mkdir` the volume data dirs, assert the
150
+ Manager gate, start the ancillary services, and **launch ComfyUI from the
151
+ baked venv**.
152
+ 6. `sleep infinity` — keeps the pod alive.
153
+
154
+ So SSH + Jupyter + nginx come from the base; **our hook adds everything else**.
155
+
156
+ ---
157
+
158
+ ## ComfyUI launch command (the exact flags)
159
+
160
+ `/post_start.sh` launches (from `cd /opt/ComfyUI`, with the baked venv python):
161
+
162
+ ```bash
163
+ /opt/ComfyUI/venv/bin/python main.py \
164
+ --listen 0.0.0.0 --port 3001 \
165
+ --enable-manager \
166
+ --use-pytorch-cross-attention \
167
+ --user-directory /workspace/user \
168
+ --input-directory /workspace/input \
169
+ --output-directory /workspace/output \
170
+ --extra-model-paths-config /opt/ComfyUI/extra_model_paths.yaml
171
+ ```
172
+
173
+ * **`--user-directory /workspace/user`** — workflows, ComfyUI settings, AND the
174
+ Manager config (see [Manager gate](#manager-remote-install-gate)) live on the
175
+ volume.
176
+ * **`--input-directory` / `--output-directory`** — inputs and generated images on
177
+ the volume.
178
+ * **`--extra-model-paths-config`** — points every model category at
179
+ `/workspace/models` (see below). MODELS persist on the volume.
180
+ * **We deliberately do NOT use `--base-directory`.** It would relocate
181
+ `custom_nodes` (and temp) onto the volume too — but we want `custom_nodes` to
182
+ stay **in the image**. The per-dir flags + `extra_model_paths.yaml` give us
183
+ exactly the split we want (data on the volume, software in the image).
184
+
185
+ > **Flag verification.** These flag names were verified against ComfyUI `master`
186
+ > (`comfy/cli_args.py`): `--user-directory`, `--input-directory`,
187
+ > `--output-directory`, `--base-directory`, and `--extra-model-paths-config` all
188
+ > exist with these exact spellings, and `--base-directory`'s own help text states
189
+ > it sets the base for "models, custom_nodes, input, output, temp, and user
190
+ > directories" — confirming why we avoid it. **Assumption to confirm at build:**
191
+ > the *pinned* `COMFYUI_REF` you bake actually ships these flags (they are present
192
+ > on recent `master`; an old pin may not have them). Run
193
+ > `"/opt/ComfyUI/venv/bin/python" /opt/ComfyUI/main.py --help` in the built image
194
+ > to confirm before publishing.
195
+
196
+ ---
197
+
198
+ ## Model paths (`extra_model_paths.yaml`)
199
+
200
+ `extra_model_paths.yaml` is baked at `/opt/ComfyUI/extra_model_paths.yaml` and
201
+ loaded via `--extra-model-paths-config`. It maps **every** model category to a
202
+ subfolder under `/workspace/models`, with `is_default: true` so the volume is the
203
+ **primary** (download) location — i.e. Manager's install-model writes there and
204
+ the files persist.
205
+
206
+ ```yaml
207
+ comfyui_mcp_volume:
208
+ base_path: /workspace
209
+ is_default: true # insert these paths at the FRONT → default target
210
+
211
+ checkpoints: models/checkpoints/
212
+ configs: models/configs/
213
+ loras: models/loras/
214
+ vae: models/vae/
215
+ text_encoders: | # ComfyUI key "text_encoders" (a.k.a. CLIP)
216
+ models/text_encoders/
217
+ models/clip/
218
+ diffusion_models: | # a.k.a. UNet
219
+ models/diffusion_models/
220
+ models/unet/
221
+ clip_vision: models/clip_vision/
222
+ style_models: models/style_models/
223
+ embeddings: models/embeddings/
224
+ diffusers: models/diffusers/
225
+ vae_approx: models/vae_approx/
226
+ controlnet: |
227
+ models/controlnet/
228
+ models/t2i_adapter/
229
+ gligen: models/gligen/
230
+ upscale_models: models/upscale_models/
231
+ latent_upscale_models: models/latent_upscale_models/
232
+ hypernetworks: models/hypernetworks/
233
+ photomaker: models/photomaker/
234
+ classifiers: models/classifiers/
235
+ model_patches: models/model_patches/
236
+ audio_encoders: models/audio_encoders/
237
+ background_removal: models/background_removal/
238
+ frame_interpolation: models/frame_interpolation/
239
+ geometry_estimation: models/geometry_estimation/
240
+ optical_flow: models/optical_flow/
241
+ detection: models/detection/
242
+ ```
243
+
244
+ Notes:
245
+
246
+ * **`is_default: true`** is the load-bearing line. ComfyUI's
247
+ `add_model_folder_path(..., is_default=True)` **inserts** the path at the front
248
+ of each category's list, so `/workspace/models/<cat>` becomes the default
249
+ download target. Without it, the image's `/opt/ComfyUI/models/<cat>` (ephemeral)
250
+ would stay first and Manager downloads would **not** persist.
251
+ * **No `custom_nodes:` key** — on purpose. Mapping `custom_nodes` to the volume
252
+ would break the fast-restart contract.
253
+ * `base_path: /workspace` with `models/...` relative subpaths mirrors ComfyUI's
254
+ own `extra_model_paths.yaml.example` convention.
255
+ * The category **keys** match ComfyUI's `folder_names_and_paths` keys (verified
256
+ against `master`'s `folder_paths.py`). The category list mirrors the example's
257
+ full set; harmless if a future ComfyUI renames one.
258
+ * `/post_start.sh` pre-creates the matching subfolders under `/workspace/models`
259
+ so they exist on a cold volume.
260
+
261
+ ### Warm model volume (skip the cold HF pull)
262
+
263
+ A **fresh** network volume has an empty `models/` tree — the first time the
264
+ agent (or you) needs a checkpoint, it's a cold download from HuggingFace, which
265
+ can be tens of GB and the slowest part of getting a new pod usable.
266
+
267
+ The fix is the same idea as the image itself: **build once, reuse many times.**
268
+
269
+ 1. **Build a seed volume once.** Spin up a pod with this image + a network
270
+ volume, download the checkpoints/LoRAs/VAEs you use most (the exact same
271
+ files the [installer packs](../../packs) already fetch — `packs/*/install-runpod.sh`
272
+ lists the HF URLs per pack) into that volume's `models/<category>/` tree
273
+ matching the layout in `extra_model_paths.yaml` above, then stop the pod.
274
+ That volume is now your **warm seed** — keep it around, don't delete it.
275
+ 2. **Reuse it for new pods**, by whichever mechanism your RunPod plan supports:
276
+ - **Volume snapshot/clone** (if available on your account) — instantly fork
277
+ a new volume from the seed's current contents instead of attaching the
278
+ same volume serially.
279
+ - **Attach the seed volume directly** to a new pod deployment (stop any pod
280
+ currently using it first) — simplest, but one pod at a time.
281
+ - **A shared object store + boot-time sync** — the most portable option, and
282
+ the only one that supports many pods pulling concurrently: keep the seed
283
+ models in an S3-compatible bucket (RunPod's own network storage, or any
284
+ S3-compatible provider) and add an `rclone sync` (or `aws s3 sync`) step to
285
+ `post_start.sh` before the model-subfolder `mkdir -p` block, pulling only
286
+ what's missing on that pod's volume. This also composes cleanly with
287
+ `PANEL_AUTO_UPDATE` above — both are "pull what's missing/changed at boot,
288
+ skip what's already there" patterns.
289
+
290
+ None of this requires a new mechanism in the image itself — `extra_model_paths.yaml`
291
+ already treats `/workspace/models` as the single source of truth regardless of
292
+ *how* files got there, so a warm volume (or a boot-time sync into one) is
293
+ transparent to ComfyUI and the agent.
294
+
295
+ ---
296
+
297
+ ## Manager (remote-install gate)
298
+
299
+ * **Manager v2 (pip), gated by `--enable-manager`.** `comfyui_manager==4.2.2`
300
+ exposes `/v2/manager/queue/task` (the API comfyui-mcp talks to). The classic
301
+ `custom_nodes/ComfyUI-Manager` checkout **conflicts** with it and is removed.
302
+ * **Where the config lives.** With `--user-directory /workspace/user`,
303
+ `comfyui_manager` reads its `config.ini` from **under the user dir** — verified
304
+ against the Manager source (`manager_migration.get_manager_path(user_dir)`):
305
+ * new ComfyUI (System User API): `…/user/__manager/config.ini`
306
+ * older ComfyUI: `…/user/default/ComfyUI-Manager/config.ini`
307
+
308
+ Because that's on the **volume**, the gate persists. `/post_start.sh` writes/re-
309
+ asserts **both** locations on every boot (idempotent), so it's correct on either
310
+ Manager build.
311
+ * **Gate values** (`config.ini`):
312
+
313
+ | Key | Value | Why |
314
+ |-----|-------|-----|
315
+ | `network_mode` | `personal_cloud` | required — the `/v2` install gate only allows remote installs in this mode |
316
+ | `security_level` | `normal-` | safe default; set `weak` (env `COMFY_SECURITY_LEVEL=weak`) for no guardrails, trusted pods only |
317
+
318
+ ---
319
+
320
+ ## ComfyUI / torch specifics
321
+
322
+ * **cu128 torch, no xformers.** The venv gets a clean `torch torchvision
323
+ torchaudio` from `https://download.pytorch.org/whl/cu128` (sm_120 kernels for
324
+ Blackwell / RTX 5090). xformers is **omitted** — its prebuilt wheels lag the
325
+ cu128 ABI — so ComfyUI launches with `--use-pytorch-cross-attention` (PyTorch
326
+ SDPA).
327
+ * **Caches are ephemeral (in the container), by design.** We do **not** redirect
328
+ HF/pip/torch/npm caches to `/workspace`. Model downloads via Manager go to
329
+ `/workspace/models` and persist — that's the only persistence we need. (First
330
+ use after a restart may re-download small ancillary files into the ephemeral
331
+ cache; the big model weights stay on the volume.)
332
+
333
+ ---
334
+
335
+ ## Services (replicating the aitrepreneur set)
336
+
337
+ | Service | Port | Source | Notes |
338
+ |---------|------|--------|-------|
339
+ | **nginx** | **3000** (→ ComfyUI 3001) | our `nginx.conf` | websocket-aware; "starting" fallback page |
340
+ | **ComfyUI** | 3001 (internal) | baked image venv | launched from `/opt/ComfyUI`; see [launch flags](#comfyui-launch-command-the-exact-flags) |
341
+ | **sshd** | 22 | base | RunPod `$PUBLIC_KEY` injection |
342
+ | **JupyterLab** | 8888 | base | set `JUPYTER_PASSWORD` |
343
+ | **code-server** | 8081 (→ 8080) | installed at build | best-effort |
344
+ | **app-manager** | 8001 (→ 8000) | COPY from donor | Node app; best-effort (needs `node`) |
345
+ | **runpod-uploader** | — | COPY from donor | file uploader; best-effort |
346
+ | **croc** | — | COPY from donor | on-demand P2P transfer |
347
+ | **cron** | — | apt | best-effort |
348
+
349
+ All ancillary services are launched **best-effort** — if a binary is absent (e.g.
350
+ you dropped the donor COPYs) the entrypoint logs `skip` and carries on, so the
351
+ image still boots ComfyUI fine.
352
+
353
+ ---
354
+
355
+ ## Topology: where the agent runs
356
+
357
+ ```
358
+ YOUR LAPTOP RUNPOD POD (this image)
359
+ ┌───────────────────────────┐ ┌───────────────────────────────────┐
360
+ │ npx comfyui-mcp connect … │ HTTP/WS ───▶ │ nginx :3000 ─▶ ComfyUI :3001 │
361
+ │ └─ panel orchestrator │ │ ├─ Manager v2 (--enable-manager) │
362
+ │ (Claude/Codex Agent SDK│ ◀─── events │ └─ Agent Panel (sidebar) │
363
+ │ on YOUR subscription) │ │ + sshd / jupyter / code-server │
364
+ └───────────────────────────┘ └───────────────────────────────────┘
365
+ ```
366
+
367
+ The **panel orchestrator** (the autonomous agent loop, on your Claude **or**
368
+ ChatGPT subscription — no API key) runs **on your machine**, not the pod. The pod
369
+ only serves ComfyUI + Manager + the panel UI. That's why **Node.js for the agent,
370
+ the Agent SDK and any LLM client are intentionally absent** — they'd burn pod
371
+ GPU-hours for nothing.
372
+
373
+ ---
374
+
375
+ ## Deploy on RunPod
376
+
377
+ Create a **Pod template** (or fill these on a one-off GPU pod):
378
+
379
+ | Template field | Value |
380
+ |----------------|-------|
381
+ | **Container image** | `<your-registry>/comfyui-mcp-runpod:<tag>` (after build & push) |
382
+ | **Container disk** | ≥ 30 GB (the baked ComfyUI + venv + cu128 torch + any runtime-installed nodes/caches, which are ephemeral) |
383
+ | **Volume disk** | e.g. 100 GB+ (your **models + workflows + I/O** live here; size for your models) |
384
+ | **Volume mount path** | **`/workspace`** |
385
+ | **Expose HTTP ports** | **`3000`** (nginx → ComfyUI). Optionally `8081` (code-server), `8001` (app-manager), `8888` (Jupyter). |
386
+ | **Expose TCP ports** | `22` (SSH) |
387
+ | **GPU** | RTX 5090 / any Blackwell or Ada card (cu128 covers both) |
388
+ | **Host driver** | >= 570 (CUDA 12.8) for the default image; >= 580 (CUDA 13) for the `:cu130` perf variant. The entrypoint preflights this (`MIN_DRIVER`) and holds the pod open with a clear message instead of crash-looping. |
389
+
390
+ **Environment variables** (Pod → Environment):
391
+
392
+ | Env | Default | Purpose |
393
+ |-----|---------|---------|
394
+ | `JUPYTER_PASSWORD` | *(unset)* | set to enable JupyterLab on :8888 (base behavior) |
395
+ | `PUBLIC_KEY` | *(RunPod injects)* | SSH public key (base behavior) |
396
+ | `COMFY_SECURITY_LEVEL` | `weak` | Manager security level — `weak` lets the agent install nodes from git URLs (single-user pod); set `normal-` to restore Manager's guardrails |
397
+ | `COMFY_NETWORK_MODE` | `personal_cloud` | must stay `personal_cloud` for remote installs |
398
+ | `COMFY_AUTOUPDATE_MANAGER` | `1` | fast-forward `comfyui_manager` (pip) at boot: `1` = latest stable release, `nightly` = ComfyUI-Manager git main, `0` = keep the baked pin. The venv is ephemeral, so this is the only Manager fix that survives a restart short of a new image. Best-effort (180s cap), never blocks boot. |
399
+ | `COMFY_EXTRA_ARGS` | *(empty)* | extra ComfyUI flags appended verbatim by the entrypoint |
400
+ | `COMFY_HOME` | `/opt/ComfyUI` | baked ComfyUI path (rarely overridden) |
401
+ | `WORKSPACE` | `/workspace` | network-volume mount (rarely overridden) |
402
+ | `PANEL_AUTO_UPDATE` | `1` | fast-forward the Agent Panel to its latest release on every boot (git fetch + reset --hard, before ComfyUI launches) — reaches pods without a new image build. Automatically a no-op on a `PANEL_REF`-pinned build (detached HEAD). Set `0` to disable. |
403
+ | `HF_TOKEN` (or `HUGGINGFACE_TOKEN`) | *(unset)* | HuggingFace token for gated model downloads (exported to ComfyUI + Manager) |
404
+ | `ARIA2_DISABLE` | `0` | set `1` to skip the aria2 download sidecar (Manager then uses its slow built-in downloader) |
405
+ | `ARIA2_RPC_PORT` | `6800` | loopback port for the aria2 RPC daemon |
406
+ | `ARIA2_RPC_SECRET` | *(random per boot)* | RPC secret for the aria2 daemon (auto-generated; set only if you need a fixed one) |
407
+ | `HF_HUB_ENABLE_HF_TRANSFER` | `1` (set at boot) | Rust parallel downloader for `huggingface_hub` fetches. **Set `0` as the FIRST troubleshooting step if an HF download fails** — hf_transfer trades resume robustness/proxy support for speed |
408
+ | `HF_XET_HIGH_PERFORMANCE` | `1` (set at boot) | high-performance mode for Xet-backed HF repos (newer hub versions) |
409
+
410
+ ### Fast model downloads (aria2 sidecar)
411
+
412
+ ComfyUI-Manager's built-in model downloader is single-stream with tiny chunks
413
+ and collapses to **<1–4 MB/s** against the MooseFS network volume — a 13 GB
414
+ model then takes hours, and because Manager's install queue is serial,
415
+ everything behind it looks wedged ("downloads don't work"). Measured on a live
416
+ pod (2026-07-07): Manager at 792 kB/s while `curl` on the same pod pulled the
417
+ same HuggingFace file at 15–80 MB/s and wrote the volume at 60–300 MB/s.
418
+
419
+ The image therefore bakes **aria2** and runs a loopback, secret-gated RPC
420
+ daemon at boot (post_start §4.4), hardened per review:
421
+
422
+ - **Supervised**: aria2c runs under a respawn loop — a crashed daemon would
423
+ otherwise leave Manager hard-failing with *no* fallback (Manager commits to
424
+ the aria2 path at import time).
425
+ - **Readiness-probed**: `COMFYUI_MANAGER_ARIA2_SERVER`/`SECRET` are exported
426
+ only after a real `aria2.getVersion` RPC answers (daemonizing successfully
427
+ says nothing about the listener). On probe failure the sidecar is torn down
428
+ and Manager keeps its built-in downloader.
429
+ - **`/models` → `/workspace/models` symlink**: Manager's aria2 path joins
430
+ *relative* model dirs onto `/models` — without the link an edge case would
431
+ write models onto the ephemeral container fs and lose them on restart.
432
+ - The RPC secret lives in a `0600` conf file (not the process cmdline) and is
433
+ length-checked; the live server+secret are written to
434
+ `/var/lib/comfyui-mcp/aria2-rpc.env` (root-only) so `test_image.sh` and a
435
+ debugging human can make a real RPC call.
436
+
437
+ Note Manager's aria2 path (like its built-in one) does not attach `HF_TOKEN`
438
+ to download requests — gated-model fetches need a token-authenticated URL
439
+ either way.
440
+
441
+ aria2 only covers Manager's install-model route. The **other** fetch path —
442
+ custom nodes (Impact, WAS, …) and ComfyUI internals downloading via
443
+ `huggingface_hub` — is accelerated separately: the image bakes `hf_transfer`
444
+ (HF's Rust parallel downloader) + `hf_xet`, and boot defaults
445
+ `HF_HUB_ENABLE_HF_TRANSFER=1` + `HF_XET_HIGH_PERFORMANCE=1` **only after
446
+ verifying `hf_transfer` imports** (with the flag set and the package missing,
447
+ `huggingface_hub` raises at download time — the integrity gate asserts the
448
+ import for the same reason). Both are plain pod env opt-outs (`=0`); flip
449
+ `HF_HUB_ENABLE_HF_TRANSFER=0` first when debugging a failing HF download.
450
+
451
+ > **First boot vs warm restart:** both are fast. First boot additionally creates
452
+ > the volume data dirs and copies the spotcheck model (if baked); warm restart
453
+ > skips even those. Watch the pod log / the "starting" page; ComfyUI init is
454
+ > ~30-60s either way.
455
+
456
+ ---
457
+
458
+ ## Build & push
459
+
460
+ No GPU is needed at **build** time. On a machine with Docker + BuildKit:
461
+
462
+ ```bash
463
+ cd docker/runpod
464
+
465
+ # Default: baked software + spotcheck model.
466
+ docker build -t <your-registry>/comfyui-mcp-runpod:cu128 .
467
+ docker push <your-registry>/comfyui-mcp-runpod:cu128
468
+
469
+ # Omit the baked spotcheck model (the 6.9 GB layer is never downloaded):
470
+ docker build --build-arg BAKE_SPOTCHECK_MODEL=0 \
471
+ -t <your-registry>/comfyui-mcp-runpod:cu128-noseed .
472
+ ```
473
+
474
+ ### Test before you push (post-build gate)
475
+
476
+ The build itself ends in an **integrity gate** (Dockerfile §9.5) that fails on
477
+ missing/0-byte panel files, a broken venv, or a `post_start.sh` syntax error.
478
+ Before any registry push, run the deeper suite:
479
+
480
+ ```bash
481
+ # static checks + a real 2-boot volume test + the ENOSPC self-heal test:
482
+ ./test_image.sh <image-ref>
483
+
484
+ # static checks only (~seconds):
485
+ ./test_image.sh <image-ref> --static
486
+ ```
487
+
488
+ For the official Docker Hub image, release **npm-publish style** — one command
489
+ that versions, builds, gates, publishes, verifies, and pins the template:
490
+
491
+ ```bash
492
+ npm run runpod:release # minor bump: 1.6 -> 1.7
493
+ npm run runpod:release:major # major bump: 1.6 -> 2.0
494
+ npm run runpod:release -- 2.3 # explicit tag
495
+ ```
496
+
497
+ The script (`scripts/runpod-release.mjs`): resolves the next version from
498
+ Docker Hub → builds the fat image (extras donor = the previous release, so no
499
+ 63 GB pull; panel layer cache-busted to the panel repo's main SHA) → runs the
500
+ full `test_image.sh` suite via `deploy-dockerhub.sh` (**a failure aborts before
501
+ anything is pushed**) → pushes `:<version>` + `:latest` → `verify_image_remote.py`
502
+ checks what the registry actually serves → pins the RunPod template to the new
503
+ version tag via the GraphQL API (`RUNPOD_API_KEY`; `SKIP_TEMPLATE=1` to skip).
504
+
505
+ The two lower-level pieces also work standalone:
506
+
507
+ ```bash
508
+ docker build -t artokun/comfyui-mcp-runpod:build .
509
+ ./deploy-dockerhub.sh 1.7 # gate + push :1.7 + :latest + remote verify
510
+ ```
511
+
512
+ CI does the same for `ghcr.io/...:cu128-lean` (`verify_image_remote.py`
513
+ inspects the pushed manifest + seed layer without pulling the 20 GB image).
514
+
515
+ > **Pin the RunPod template to the VERSION tag** (`:1.6`), not `:latest`.
516
+ > RunPod hosts cache images per-tag — a template on `:latest` can silently
517
+ > serve a stale build from a host's cache; a fresh version tag can't. The
518
+ > release script does this for you. (Don't use `runpodctl template update`
519
+ > for it — it trips a "public templates cannot have Registry Credentials"
520
+ > API error on public templates; the script's GraphQL path doesn't.)
521
+
522
+ Override pins as needed:
523
+
524
+ ```bash
525
+ docker build \
526
+ --build-arg BASE_IMAGE=runpod/pytorch:1.0.7-cu1281-torch280-ubuntu2404 \
527
+ --build-arg COMFYUI_REF=master \
528
+ --build-arg COMFYUI_MANAGER_VERSION=4.2.2 \
529
+ --build-arg PANEL_REF=<tag-or-branch> \
530
+ -t <your-registry>/comfyui-mcp-runpod:cu128 .
531
+ ```
532
+
533
+ ### The 63 GB donor pull (and how to drop it)
534
+
535
+ `STAGE A` (`runpod-donor`) pulls `aitrepreneur/comfyui:2.3.5` (~63 GB) **at build
536
+ time** purely to copy out `runpod-uploader`, `croc` and `/app-manager`. It does
537
+ **not** ship in the final image, but it is a heavy one-time pull on your build
538
+ host — and won't fit on a disk-constrained CI runner. If you don't need those
539
+ extras:
540
+
541
+ ```bash
542
+ docker build --build-arg INCLUDE_RUNPOD_EXTRAS=0 \
543
+ -t <your-registry>/comfyui-mcp-runpod:cu128-lean .
544
+ ```
545
+
546
+ BuildKit only builds a stage the final target's dependency graph actually
547
+ reaches, so `INCLUDE_RUNPOD_EXTRAS=0` skips the donor pull **entirely** — not
548
+ just its `COPY` (see `runpod-extras-0`/`runpod-extras-1` in the Dockerfile,
549
+ which mirror the `BAKE_SPOTCHECK_MODEL` selector pattern below). The entrypoint
550
+ already treats `runpod-uploader`/`croc`/`app-manager` as best-effort and skips
551
+ them cleanly when absent; `croc` can instead be installed from its official
552
+ release if you want it back without the donor pull.
553
+
554
+ ### Image size note (the duplicate-torch tradeoff)
555
+
556
+ Because the software is now baked, the image is **larger** than the volume-seed
557
+ design — that's the deliberate fast-restart tradeoff (first-pull/image-size no
558
+ longer matters per the spec). The lean base `runpod/pytorch:…-cu1281-torch280-…`
559
+ **already ships a cu128 torch**, yet we install cu128 torch again into the
560
+ isolated venv (~7 GB duplicated) for a guaranteed-correct, isolated environment.
561
+ To shave that:
562
+
563
+ * Build the venv with `python -m venv --system-site-packages /opt/ComfyUI/venv`
564
+ and **skip the `pip install torch …` step**, so ComfyUI reuses the base's
565
+ cu128 torch. **Verify first** that the base torch is genuinely `+cu128`
566
+ (`python -c "import torch;print(torch.__version__, torch.version.cuda)"`) —
567
+ some older `cu1281` tags shipped a cu-default torch.
568
+
569
+ > **First build on a new base tag?** Watch for: (a) the base's
570
+ > `/start.sh` actually invokes `/post_start.sh` (it does in `runpod/containers`,
571
+ > but confirm for your exact tag); (b) `main.py --help` lists `--user-directory`,
572
+ > `--input-directory`, `--output-directory`, `--extra-model-paths-config`,
573
+ > `--enable-manager`, `--use-pytorch-cross-attention` for your pinned
574
+ > `COMFYUI_REF`; (c) `comfyui_manager 4.2.2` reads the gate from the volume user
575
+ > dir (`user/__manager/config.ini` or `user/default/ComfyUI-Manager/config.ini`);
576
+ > (d) Manager install-model writes to `/workspace/models/<cat>` (i.e. `is_default`
577
+ > took effect); (e) the donor paths `/usr/local/bin/runpod-uploader`,
578
+ > `/usr/local/bin/croc`, `/app-manager` exist. Then deploy and verify a real
579
+ > stop/start is fast.
580
+
581
+ ---
582
+
583
+ ## The one-command local connect
584
+
585
+ Once the pod is up and you have its proxy URL, on your laptop:
586
+
587
+ ```bash
588
+ npx -y comfyui-mcp@latest connect https://<pod-id>-3000.proxy.runpod.net
589
+ ```
590
+
591
+ This starts the comfyui-mcp panel orchestrator pointed at the pod. Because the pod
592
+ page is served over `https://`, `connect` automatically opens a secure, token-gated
593
+ **`wss://` tunnel** (via Cloudflare) to the agent bridge on your machine and hands
594
+ the pod's panel that URL — so the HTTPS page reaches your local agent with **no
595
+ browser prompt, in any browser** (a secure page can't open a plain `ws://` socket
596
+ to your box). Then open the pod's ComfyUI, open the **Agent Panel**, enable the
597
+ **external-orchestrator** toggle, and click **Connect**. (Add **`--insecure-bridge`**
598
+ to force the plain `ws://127.0.0.1:9180` loopback instead — e.g. when you reach the
599
+ pod via an SSH port-forward.)
600
+
601
+ If you're on a build that predates the `connect` subcommand, the equivalents are:
602
+
603
+ ```bash
604
+ # remote ComfyUI as an MCP server (for Claude Code / Desktop):
605
+ npx -y comfyui-mcp --comfyui-url https://<pod-id>-3000.proxy.runpod.net
606
+ # panel orchestrator (drives the sidebar agent):
607
+ COMFYUI_URL=https://<pod-id>-3000.proxy.runpod.net npx -y comfyui-mcp --panel-orchestrator
608
+ ```
609
+
610
+ If the pod sits behind auth, set `COMFYUI_AUTH_TOKEN` (+ `COMFYUI_AUTH_HEADER` /
611
+ `COMFYUI_AUTH_SCHEME`) on the local command.
612
+
613
+ ---
614
+
615
+ ## Owner confirmations needed
616
+
617
+ Draft pins/assumptions — confirm before building/publishing:
618
+
619
+ 1. **Base tag** — `runpod/pytorch:1.0.7-cu1281-torch280-ubuntu2404` (verified on
620
+ Docker Hub 2026-06-19). Confirm it's still the one you want at build.
621
+ 2. **Base `/start.sh` hook** — confirm your exact base tag's `/start.sh` runs
622
+ `/post_start.sh` and `service nginx start` reads `/etc/nginx/nginx.conf`
623
+ (true in `runpod/containers` `main`).
624
+ 3. **Launch flags on the pinned ComfyUI** — `--user-directory`,
625
+ `--input-directory`, `--output-directory`, `--extra-model-paths-config`,
626
+ `--enable-manager`, `--use-pytorch-cross-attention`. Verified present on
627
+ `master`; confirm for whatever `COMFYUI_REF` you bake (`main.py --help`).
628
+ 4. **`is_default` write behavior** — confirm Manager install-model actually lands
629
+ in `/workspace/models/<cat>` (it should, since `is_default` front-inserts the
630
+ volume paths).
631
+ 5. **`comfyui_manager` 4.2.2 config path** — reads
632
+ `user/__manager/config.ini` (new) or `user/default/ComfyUI-Manager/config.ini`
633
+ (older) under `--user-directory`. We write both; confirm one is honored.
634
+ 6. **Donor artifact paths** — `/usr/local/bin/runpod-uploader`,
635
+ `/usr/local/bin/croc`, `/app-manager` in `aitrepreneur/comfyui:2.3.5`.
636
+ 7. **nginx port convention** — kept **3000 → 3001** (matches your `connect`
637
+ flow). We override the base's native ComfyUI proxy block with our
638
+ self-contained `nginx.conf`.
639
+ 8. **Duplicate torch** — keep the isolated cu128 torch (default, ~+7 GB) or reuse
640
+ the base's via `--system-site-packages` (see size note).
641
+ 9. **Panel ref** — cloned from the default branch (nightly HEAD). Pin `PANEL_REF`
642
+ for reproducible images?
643
+ 10. **custom_nodes tradeoff** — confirm you accept that runtime-installed nodes
644
+ don't survive a restart (models do). Permanent nodes go in the Dockerfile.
645
+
646
+ ---
647
+
648
+ ## Files
649
+
650
+ | File | Purpose |
651
+ |------|---------|
652
+ | `Dockerfile` | Multi-stage: lean `runpod/pytorch` final stage; `aitrepreneur` donor stage for service artifacts; ARG-gated SDXL spotcheck stage. **Bakes** ComfyUI + cu128 venv + Manager v2 + Agent Panel into `/opt/ComfyUI` (run from there; never synced to the volume). |
653
+ | `post_start.sh` | Boot hook (installed as `/post_start.sh`): `mkdir` the `/workspace` data dirs, first-boot spotcheck-model copy, Manager-gate write, ancillary services, and ComfyUI launch from the baked venv with the per-dir flags. |
654
+ | `extra_model_paths.yaml` | Maps every model category to `/workspace/models/<cat>` with `is_default: true` (volume is the primary/download location). No `custom_nodes:` key (nodes stay in the image). |
655
+ | `nginx.conf` | Self-contained reverse proxy (`:3000 → :3001`, websocket-aware, "starting" fallback) + code-server / app-manager fronts. Installed over the base's. |
656
+ | `starting.html` | The auto-refreshing "ComfyUI is starting…" page nginx serves until upstreams are up. |
657
+ | `config.ini` | Manager v2 gate template (`network_mode = personal_cloud`, `security_level = normal-`), copied to the volume user dir at boot. |
658
+ | `README.md` | This document. |