This project implements a custom RunPod serverless worker that forwards requests from the RunPod platform to a ComfyUI server. It provides the required RunPod API endpoints (/run, /runsync, /health) while leveraging ComfyUI's powerful workflow execution capabilities.
The solution consists of:
- Custom Handler (
src/handler.py) - Enhanced RunPod serverless API with validation and monitoring - Start Script (
src/start.sh) - Production startup with symlinks to shared models - Docker Configuration (
Dockerfile.runpod.serverless) - Builds the complete container image - Dependencies (
requirements.txt) - Python packages for the handler
- ✅ RunPod API Compliance: Implements
/run,/runsync, and/healthendpoints - ✅ Workflow Submission: Accepts ComfyUI workflows in API format
- ✅ Image Upload Support: Handles base64-encoded input images
- ✅ Real-time Monitoring: Uses ComfyUI websocket API for job progress
- ✅ Flexible Output: Returns images as base64 or uploads to S3
- ✅ Error Handling: Comprehensive error reporting and logging
- ✅ Custom Node Support: Includes all necessary ComfyUI custom nodes
This repository supports a two-image workflow to make Python-only changes fast:
- Base image (
:base-1.0): CUDA + OS deps + torch + ComfyUI + custom nodes (rare rebuild) - App image (e.g.
:1.5.0): worker code + schemas + workflows (frequent rebuild)
The build entrypoint is build_custom.ps1.
# Build base image (rare)
./build_custom.ps1 -Target base -ImageName "fcaldas/tabario.com" -BaseImageTag "base-1.0"
# Build app image (fast for python-only changes)
./build_custom.ps1 -Target app -ImageName "fcaldas/tabario.com" -BaseImageTag "base-1.0" -ImageTag "1.5.0"docker login
# Build + push base image
./build_custom.ps1 -Target base -ImageName "fcaldas/tabario.com" -BaseImageTag "base-1.0" -Push
# Build + push app image
./build_custom.ps1 -Target app -ImageName "fcaldas/tabario.com" -BaseImageTag "base-1.0" -ImageTag "1.5.0" -Pushcd y:\projects\runpod-serverless
docker build `
-f .\Dockerfile.runpod.serverless `
-t fcaldas/tabario.com:1.3 `
.docker login
docker push fcaldas/tabario.com:1.3- Go to RunPod Console → Serverless → Templates
- Create new template with:
- Container Image:
fcaldas/tabario.com:1.3 - Container Disk: 100 GB
- GPU: 16-24 GB VRAM recommended
- Environment Variables:
CIVITAI_API_KEY=<token>when using Civitai-hosted models (only needed if you later enable runtime downloads)
- Container Image:
- Deploy the template as a serverless endpoint
This container uses a RunPod Network Volume mounted at /runpod-volume. On startup, it creates symlinks from /runpod-volume/comfyui/models/<subfolder> to /comfyui/models/<subfolder> so ComfyUI can load shared models without downloading.
Ensure your volume contains the expected ComfyUI subfolders and files, e.g. vae, clip, unet, loras, checkpoints, etc.
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d @test_input.json \
https://api.runpod.ai/v2/YOUR_ENDPOINT_ID/runsynccurl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d @test_input.json \
https://api.runpod.ai/v2/YOUR_ENDPOINT_ID/run{
"id": "sync-uuid-string",
"status": "COMPLETED",
"output": {
"images": [
{
"filename": "ComfyUI_00001_.png",
"type": "base64",
"data": "iVBORw0KGgoAAAANSUhEUg..."
}
]
},
"delayTime": 123,
"executionTime": 4567
}| Variable | Default | Description |
|---|---|---|
COMFY_HOST |
127.0.0.1:8188 |
ComfyUI server address |
COMFY_LOG_LEVEL |
DEBUG |
ComfyUI logging level |
BUCKET_ENDPOINT_URL |
- | S3 endpoint for image uploads |
CIVITAI_API_KEY |
- | Required to download Civitai-hosted models (e.g., t2i-chroma-anime) |
To enable S3 uploads instead of base64 responses:
# Set these environment variables in your RunPod template
BUCKET_ENDPOINT_URL=https://s3.amazonaws.com
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
BUCKET_NAME=your_bucket_nameThe handler supports multiple ComfyUI workflow templates via the comfyui_workflow_name input.
Use comfyui_workflow_name: "video_wan2_2_14B_i2v" (default). This workflow requires an input image and uses width, height, and length.
{
"input": {
"prompt": "A beautiful sunset over the ocean with waves crashing",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
"width": 480,
"height": 640,
"length": 81,
"comfyui_workflow_name": "video_wan2_2_14B_i2v"
}
}Use comfyui_workflow_name: "image_qwen_t2i". This workflow does not require image. The handler injects:
promptinto{{ IMAGE_PROMPT }}widthinto{{ IMAGE_WIDTH }}heightinto{{ IMAGE_HEIGHT }}
as defined in workflows/image_qwen_image_distill_official_comfyui.json (notably the EmptySD3LatentImage node).
{
"input": {
"prompt": "Slow cinematic push-in through the forest, 4K",
"width": 720,
"height": 1280,
"comfyui_workflow_name": "image_qwen_t2i"
}
}- prompt (string): Text prompt used by the selected workflow
- image (string): Base64-encoded input image (with or without data URI prefix). Required only for workflows that include
{{ INPUT_IMAGE }}(e.g. Wan 2.2 I2V). - width (int): Output width in pixels (default: 480)
- height (int): Output height in pixels (default: 640)
- length (int): Number of frames to generate (default: 81). Used by video workflows.
- comfyui_workflow_name (string): Workflow template key (default:
video_wan2_2_14B_i2v). For Qwen T2I useimage_qwen_t2i.
The handler will:
- Load the selected workflow template (
comfyui_workflow_name) - If the workflow contains
{{ INPUT_IMAGE }}, upload the providedimageto ComfyUI and inject the uploaded filename - Inject the prompt into the appropriate placeholder (e.g.
{{ VIDEO_PROMPT }},{{ POSITIVE_PROMPT }}, or{{ IMAGE_PROMPT }}depending on the workflow) - Inject
width/height(andlengthfor video workflows) into the workflow - Queue the workflow for processing
- ComfyUI-GGUF (GGUF model support)
- ComfyUI-Frame-Interpolation (Video interpolation)
- ComfyUI-VideoHelperSuite (Video processing utilities)
- ComfyUI_ExtraModels (Additional model formats)
- ComfyUI-Unload-Model (Memory management)
- ComfyUI-Easy-Use (Utility nodes)
- Stable Diffusion: v1-5-pruned-emaonly-fp16.safetensors
- Wan 2.2: I2V models (High/Low noise GGUF)
- VAEs: wan_2.1_vae.safetensors, wan2.2_vae.safetensors, qwen_image_vae.safetensors
- Text Encoders: umt5-xxl-encoder-Q5_K_M.gguf, qwen_2.5_vl_7b_fp8_scaled.safetensors
- LoRAs: Wan2.2 Lightning LoRAs
- Upscalers: RealESRGAN_x2plus.pth
- Frame Interpolation: rife47.pth
Modify Dockerfile.runpod.serverless:
# Add custom nodes
RUN comfy-node-install https://github.com/your-repo/custom-node
# Download models
RUN wget -O models/checkpoints/your_model.safetensors https://url-to-model-
ComfyUI fails to start
- Check logs for model loading errors
- Verify GPU memory is sufficient
- Ensure all models downloaded successfully
-
Handler not responding
- Verify ComfyUI is accessible:
curl http://127.0.0.1:8188/system_stats - Check websocket connection:
ws://127.0.0.1:8188/ws
- Verify ComfyUI is accessible:
-
S3 upload failures
- Verify AWS credentials and permissions
- Check bucket name and region
- Ensure endpoint URL is correct
-
Video workflows stuck IN_PROGRESS even though the mp4 exists in
/comfyui/output- ComfyUI history is keyed by the ComfyUI
prompt_id(returned fromPOST /prompt), not the RunPod job id. - To inspect history, query:
curl http://127.0.0.1:8188/historycurl http://127.0.0.1:8188/history/<prompt_id>
- With
ComfyUI-VideoHelperSuite, final mp4 outputs may appear underoutputs.<node>.gifs(even though the file is an.mp4). Older worker versions that only looked forimages/videosin history could miss the mp4 and keep polling for “final assets”.
- ComfyUI history is keyed by the ComfyUI
Enable detailed logging:
# Set environment variable
COMFY_LOG_LEVEL=DEBUG
# Or modify Dockerfile
ENV COMFY_LOG_LEVEL=DEBUGrunpod-serverless/
├── src/
│ ├── handler.py # Enhanced RunPod handler with validation
│ └── start.sh # Production startup script
├── Dockerfile.runpod.serverless # Docker build configuration
├── requirements.txt # Python dependencies
├── test_input.json # Example workflow for testing
├── save_base64_image.py # Helper: decode base64 output image from JSON and save locally
└── README.md # This file
This project extends the RunPod worker-comfyui base image. Please refer to the respective licenses of the underlying components.