ComfyUI serverless workers (RunPod)
Run ComfyUI workflows as RunPod serverless endpoints, optimized for cold start: everything is baked into the image (models included) so a worker whose host has the image cached starts in seconds and never touches the network.
Images: plx1029/comfyui-serverless on Docker Hub. Current endpoint:
| endpoint | image tag | RunPod endpoint id | contents |
|---|---|---|---|
| qwen-edit-turbo | qwen-edit-turbo-v7 |
dom5lwr0o5wq6u |
Qwen-Image-Edit-2511 bf16 + Lightning 4/8-step loras + Skin-Fix/Qwen4Play loras, workflow qwen-edit-turbo-v4 |
| qwen-edit-nsfw | qwen-edit-nsfw-v1 |
tplcz9jtzihoxa |
Qwen-Rapid-AIO-NSFW v11.4 + next-scene lora, RES4LYF sampler, workflow qwen-edit-nsfw |
| faceswap-lissie | faceswap-lissie-v1 |
qkeibkeoefcrk3 |
Flux-2-Klein 9B + Qwen3-8B TE + bfs-head/lissie loras + Qwen3-VL-4B + RMBG-2.0 (baked), workflow new-faceswap-lissie-v4 |
RunPod templates serverless-<endpoint>-v1, endpoints serverless-<endpoint>:
H100 80GB (SXM/PCIe/NVL), FlashBoot, min 0 / max 2 workers, idle timeout 60 s,
QUEUE_DELAY scaler. All endpoints are workflow-agnostic: they run their
baked workflow by default, any other workflow whose models/nodes are in the
image via "workflow", or a fully custom API-format graph via
"workflow_json" (uploaded images are matched to LoadImage nodes by name).
Serverless-specific workflow tweaks (baked copies under endpoints/*/workflows/
differ from workflows/api/ in exactly these ways):
qwen-edit-turbo(since-v7;-v1/-v2had Amateur-Photo instead): lora slot 2 of the "User LoRAs" node isQwen4Play_v2, off by default (paramslora_qwen4play/lora_qwen4play_strengthtoggle it); the Amateur-Photo lora is not in the image anymore. Since-v7,start.shpasses no VRAM flag (ComfyUI default, same as pods):--highvrampinned lora weight backups in VRAM, and full-coverage loras like Qwen4Play (all 60 blocks) OOM an 80 GB H100 that way. Perf is unchanged (~56 s vs ~57 s cold execute).qwen-edit-nsfw: removed the KJNodesWidgetToStringnode (it reads UI-only metadata and crashes headless); the Image-Saver metadata node gets a static model-name string instead. Behavior is otherwise identical.faceswap-lissie: unchanged, but the endpoint defaultskeep_vl_loaded=trueso Qwen3-VL stays in VRAM between jobs (~10 s faster warm jobs).
Layout
serverless/
βββ handler.py # generic RunPod handler (same file for every endpoint)
βββ start.sh # container CMD: boot ComfyUI headless, run handler
βββ build.sh # daemonless image builder (crane; runs on a pod)
βββ Dockerfile # reproducible equivalent for machines with Docker
βββ endpoints/
β βββ qwen-edit-turbo/
β βββ endpoint.json # models + custom nodes + default workflow
β βββ workflows/<name>.json # API-format workflow(s) baked into the image
β βββ params/<name>.json # friendly-param -> node.input mapping
βββ tests/
β βββ local_test.py # drive handler against a locally running ComfyUI
β βββ endpoint_test.py # drive a deployed RunPod endpoint
βββ client/ # standalone caller scripts (stdlib-only) + usage README
βββ qwen_edit_sync.py # /runsync: block until done
βββ qwen_edit_async.py # /run: submit, poll, fire-and-forget, collect, cancel
βββ qwen-edit-turbo-v4.json
Calling the endpoint
POST https://api.runpod.ai/v2/<ENDPOINT_ID>/runsync (or /run + /status/<id>)
{"input": {
"images": [
{"name": "main.jpg", "image": "<base64 or data-URI>"},
{"name": "ref.png", "image": "<base64>"}
],
"params": {
"prompt": "replace the outfit ...",
"mode": "turbo-8",
"seed": 123,
"input_max_dim": 2048,
"output_max_dim": 2560,
"lora_skin_fix": true
},
"set": {"43.cfg": 1.0}
}}
images[0]auto-fills theimageslot,images[1]theimage_refslot; a missing second image automatically setsuse_ref=false.mode:turbo-4|turbo-8(default) |quality.seedomitted β randomized (returned in the response).setpatches any rawNODE.INPUT(orNODE.INPUT.KEY) after params.workflowselects among baked workflows;workflow_jsonruns a full API-format graph passthrough.- Full param list per workflow:
endpoints/<ep>/params/<workflow>.json.
Response: {"images": [{"filename", "type": "base64", "data"}], "seed", "prompt_id", "workflow", "timings": {...}}.
Payload limits (RunPod): ~10 MB on /run, ~20 MB on /runsync β enough for
two photos. URL inputs / S3 outputs are a planned follow-up for bigger traffic.
Building images (no Docker needed β run on a pod)
cd serverless
# heavy tag: base + custom nodes + one layer PER model (build rarely)
./build.sh models qwen-edit-turbo v1
# tiny tag: + handler/workflows/params + CMD (build often, pushes only MBs)
./build.sh code qwen-edit-turbo v1 v1
Needs: crane + pigz installed, models on disk under
$COMFY_WORKSPACE/ComfyUI/models/, DOCKER_HUB_ACCESS_TOKEN set. The model
layers are content-addressed: rebuilding/pushing a new code version never
re-uploads them. On a machine with Docker, Dockerfile builds the same image.
Adding a new endpoint family (e.g. krea2): create
endpoints/krea2/{endpoint.json,workflows/,params/}, download its models on
the build pod (download_missing_models.sh <workflows...>), then
./build.sh all krea2 v1. The handler is already generic.
Cold start design
- Models live in image layers β cached on RunPod host NVMe after first pull; no network volume, no downloads at start.
- ComfyUI runs headless (
127.0.0.1, no Manager/frontend extras); its default memory management keeps the ~55 GB of bf16 weights resident in VRAM between jobs, so only the first job on a fresh worker pays model loading (no--highvramsince v7 β see the lora note above). - FlashBoot on the endpoint snapshots warm workers.
SERVERLESS_REFRESH=true(endpoint env) re-pulls handler/workflows/params fromhf.co/aleph65/ComfyUI/serverless/at worker boot β iterate on code and workflow JSONs with zero image rebuild (off by default to keep cold starts fully offline).
Endpoint env vars
| var | default | meaning |
|---|---|---|
SERVERLESS_ENDPOINT |
sole dir in endpoints/ |
which endpoint config to serve |
COMFY_PORT |
7865 | internal ComfyUI port |
COMFY_ARGS |
(empty) | extra ComfyUI flags |
COMFY_EXEC_TIMEOUT |
600 | per-job execution timeout (s) |
SERVERLESS_REFRESH |
false | boot-time code refresh from the HF repo |