Deploy ComfyUI AI workflows with Docker, Traefik, and Authentik
Run ComfyUI as a self-hosted AI workflow interface with persistent model folders, Docker Compose, Traefik HTTPS routing, and Authentik forward-auth protection.
ComfyUI is a node-based interface for building AI image and media workflows. It is popular for Stable Diffusion pipelines because every step is explicit: model loading, prompts, samplers, ControlNet, LoRAs, upscaling, and custom nodes can all be represented as a workflow graph.
This guide deploys ComfyUI as a standalone Docker Compose service behind Traefik and protects the web UI with Authentik.
1
2
3
4
5
6
7
8
9
Browser
↓ HTTPS
comfyui.example.com
↓
Traefik
↓ forward-auth
Authentik Embedded Outpost
↓ after login
ComfyUI container on port 8188
All domains, usernames, passwords, tokens, and IP addresses in this post are placeholders. Keep real
.envfiles, server IPs, API tokens, private hostnames, and credentials out of public documentation.
What this service does
ComfyUI provides a browser UI for building and running AI generation workflows.
In a homelab, the useful pattern is:
- keep ComfyUI inside its own project folder;
- persist models, inputs, outputs, custom nodes, and user data;
- expose only the reverse proxy publicly;
- protect the UI with SSO before anyone reaches the app;
- avoid publishing raw ComfyUI directly to the internet.
ComfyUI is powerful, but it can execute custom nodes and load large models. Treat it like an internal creative workstation, not a casual public website.
Folder layout
Use one dedicated project folder:
1
2
3
4
5
6
7
8
9
10
11
/home/ubuntu/comfyui/
├── docker-compose.yml
├── Dockerfile
├── .env
├── README.md
└── data/
├── models/
├── input/
├── output/
├── user/
└── custom_nodes/
Create the directories:
1
2
mkdir -p /home/ubuntu/comfyui/data/{models,input,output,user,custom_nodes}
cd /home/ubuntu/comfyui
The important rule: models and generated files live under data/, not inside the disposable container layer.
Environment file
Create /home/ubuntu/comfyui/.env:
COMFYUI_HOST=comfyui.example.com
Use your real domain only on your server. In public docs, keep examples generic.
Dockerfile
This Dockerfile builds ComfyUI from the official repository and installs the Python dependencies.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
COMFYUI_PATH=/opt/ComfyUI
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git \
ca-certificates \
libgl1 \
libglib2.0-0 \
libgomp1 \
ffmpeg \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt
RUN git clone --depth=1 https://github.com/comfy-org/ComfyUI.git "$COMFYUI_PATH"
WORKDIR /opt/ComfyUI
RUN python -m pip install --upgrade pip setuptools wheel \
&& python -m pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu \
&& python -m pip install -r requirements.txt
EXPOSE 8188
CMD ["python", "main.py", "--listen", "0.0.0.0", "--port", "8188", "--cpu"]
This example uses CPU PyTorch so it works on a generic server without GPU runtime setup.
If your host has an NVIDIA GPU, switch to a CUDA-capable base image, install CUDA PyTorch, add the NVIDIA container runtime settings, and remove --cpu.
Docker Compose service
Create /home/ubuntu/comfyui/docker-compose.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
services:
comfyui:
build:
context: .
dockerfile: Dockerfile
container_name: comfyui
restart: unless-stopped
init: true
env_file: .env
volumes:
- ./data/models:/opt/ComfyUI/models
- ./data/input:/opt/ComfyUI/input
- ./data/output:/opt/ComfyUI/output
- ./data/user:/opt/ComfyUI/user
- ./data/custom_nodes:/opt/ComfyUI/custom_nodes
ports:
- "127.0.0.1:8188:8188"
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.comfyui.entrypoints=http"
- "traefik.http.routers.comfyui.rule=Host(`${COMFYUI_HOST}`)"
- "traefik.http.middlewares.comfyui-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.comfyui.middlewares=comfyui-https-redirect"
- "traefik.http.routers.comfyui-secure.entrypoints=https"
- "traefik.http.routers.comfyui-secure.rule=Host(`${COMFYUI_HOST}`)"
- "traefik.http.routers.comfyui-secure.tls=true"
- "traefik.http.routers.comfyui-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.comfyui-secure.middlewares=authentik@docker"
- "traefik.http.routers.comfyui-secure.service=comfyui"
- "traefik.http.services.comfyui.loadbalancer.server.port=8188"
healthcheck:
test: ["CMD-SHELL", "python - <<'PY'\nimport urllib.request\nurllib.request.urlopen('http://127.0.0.1:8188/', timeout=5).read(1)\nPY"]
interval: 60s
timeout: 10s
retries: 5
start_period: 180s
networks:
proxy:
external: true
Two details matter here:
127.0.0.1:8188:8188keeps the raw app bound to localhost only.authentik@dockertells Traefik to ask Authentik before forwarding users to ComfyUI.
Start ComfyUI
Build and start the service:
1
2
cd /home/ubuntu/comfyui
docker compose up -d --build
Check status:
1
2
docker compose ps
docker compose logs -f comfyui
A healthy startup should eventually show something like:
1
2
Starting server
To see the GUI go to: http://0.0.0.0:8188
Test locally from the host:
1
curl -I http://127.0.0.1:8188/
Expected result:
1
HTTP/1.1 200 OK
Add the Authentik application
The Traefik middleware is not enough by itself. Authentik also needs a proxy provider, an application, and an outpost route for this hostname.
In Authentik, create:
1
2
3
4
5
6
7
Application name: ComfyUI
Slug: comfyui
Launch URL: https://comfyui.example.com/
Provider type: Proxy Provider
External host: https://comfyui.example.com
Mode: Forward auth, single application
Cookie domain: example.com
Then attach the provider to the authentik Embedded Outpost.
If the provider is missing from the outpost, the app may return a 404 from Authentik instead of redirecting to login.
Add the Authentik callback route in Traefik
When using Authentik embedded outpost with Traefik, route the outpost callback path for every protected hostname.
Add labels to the Authentik server service:
1
2
3
4
5
6
7
labels:
- "traefik.http.routers.authentik-comfyui-outpost.entrypoints=https"
- "traefik.http.routers.authentik-comfyui-outpost.rule=Host(`comfyui.example.com`) && PathPrefix(`/outpost.goauthentik.io/`)"
- "traefik.http.routers.authentik-comfyui-outpost.priority=100"
- "traefik.http.routers.authentik-comfyui-outpost.tls=true"
- "traefik.http.routers.authentik-comfyui-outpost.tls.certresolver=cloudflare"
- "traefik.http.routers.authentik-comfyui-outpost.service=authentik"
Recreate Authentik after changing labels:
1
2
cd /home/ubuntu/authentik
docker compose up -d server worker
Then recreate ComfyUI if you changed its labels:
1
2
cd /home/ubuntu/comfyui
docker compose up -d
Verification checklist
Check the container:
1
2
cd /home/ubuntu/comfyui
docker compose ps
Check direct local app response:
1
curl -I http://127.0.0.1:8188/
Check Authentik outpost callback routing through Traefik:
1
2
curl -k -I --resolve comfyui.example.com:443:127.0.0.1 \
https://comfyui.example.com/outpost.goauthentik.io/ping
Expected result:
1
HTTP/2 204
Check unauthenticated root access:
1
2
curl -k -I --resolve comfyui.example.com:443:127.0.0.1 \
https://comfyui.example.com/
Expected result:
1
2
HTTP/2 302
location: https://auth.example.com/application/o/authorize/...
That proves:
- Traefik sees the ComfyUI router;
- the callback route reaches Authentik;
- Authentik knows the ComfyUI provider;
- unauthenticated users are redirected to SSO instead of reaching the UI.
Troubleshooting
Authentik returns 404
This usually means one of these is missing:
- the ComfyUI Proxy Provider was not created;
- the provider was not attached to the embedded outpost;
- the
/outpost.goauthentik.io/route for the ComfyUI hostname was not added; - the app external host does not exactly match the requested host.
Confirm the provider external host:
1
https://comfyui.example.com
Do not use http://, do not add a trailing path, and make sure it matches the Traefik router host.
ComfyUI is reachable without login
Check the ComfyUI labels:
1
docker inspect comfyui --format ''
The secure router should use:
1
traefik.http.routers.comfyui-secure.middlewares=authentik@docker
Remove any temporary Basic Auth labels if you do not want Traefik-native authentication.
Container is healthy but public URL fails
Check three layers separately:
1
2
3
curl -I http://127.0.0.1:8188/
docker network inspect proxy
curl -k -I --resolve comfyui.example.com:443:127.0.0.1 https://comfyui.example.com/
If local works but Traefik does not, inspect labels and network membership. If local Traefik works but public DNS does not, check your DNS record or Cloudflare companion logs.
CPU mode is slow
CPU mode is good for service validation and light workflows, but serious generation wants a GPU.
For NVIDIA GPU hosts, plan for:
- NVIDIA driver on the host;
- NVIDIA Container Toolkit;
- CUDA PyTorch inside the image;
- Compose GPU device reservations;
- removing the
--cpuflag.
Using Google Colab as a temporary GPU worker
A self-hosted ComfyUI server does not need to own the GPU. A practical low-cost pattern is to keep the public, authenticated ComfyUI UI on the server and attach a temporary Google Colab runtime as a private GPU worker.
1
2
3
4
5
6
7
Browser
↓
Self-hosted ComfyUI master
↓ private tailnet
Google Colab ComfyUI worker
↓
Google Drive model storage
In this pattern:
- the master is the always-on ComfyUI instance behind Traefik and Authentik;
- the worker is a temporary Colab runtime with a GPU;
- model files live in Google Drive so they survive Colab runtime resets;
- the private connection uses a tailnet such as Headscale/Tailscale instead of a public tunnel;
- workflows are queued from the master UI, then delegated to the Colab worker.
This is useful for experimentation, but it is not the same reliability level as a real GPU server. Colab runtimes can disconnect, idle out, lose local files, or temporarily have no GPU available.
Persistent Google Drive model layout
Mount Google Drive in Colab and keep model files under one stable base folder:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/content/drive/MyDrive/ComfyUI/
├── models/
│ ├── checkpoints/
│ ├── diffusion_models/
│ ├── unet/
│ ├── vae/
│ ├── clip/
│ ├── clip_vision/
│ ├── text_encoders/
│ ├── loras/
│ ├── controlnet/
│ ├── upscale_models/
│ ├── embeddings/
│ └── qwen/
├── input/
├── output/
├── user/
└── temp/
Then symlink the Colab ComfyUI folders to Drive:
1
2
3
4
5
6
7
8
9
10
DRIVE_BASE=/content/drive/MyDrive/ComfyUI
COMFY=/content/ComfyUI
mkdir -p "$DRIVE_BASE"/{models,input,output,user,temp}
rm -rf "$COMFY"/models "$COMFY"/input "$COMFY"/output "$COMFY"/user "$COMFY"/temp
ln -s "$DRIVE_BASE/models" "$COMFY/models"
ln -s "$DRIVE_BASE/input" "$COMFY/input"
ln -s "$DRIVE_BASE/output" "$COMFY/output"
ln -s "$DRIVE_BASE/user" "$COMFY/user"
ln -s "$DRIVE_BASE/temp" "$COMFY/temp"
This keeps large downloads and generated files persistent even when Colab deletes the runtime VM.
Download models directly into Drive
Download large models from inside Colab, not through a home connection. For Hugging Face repositories, use huggingface_hub:
1
2
3
4
5
6
7
from huggingface_hub import snapshot_download
snapshot_download(
repo_id="Qwen/Qwen-Image-Layered",
local_dir="/content/drive/MyDrive/ComfyUI/models/qwen/Qwen-Image-Layered",
local_dir_use_symlinks=False,
)
For single checkpoint URLs, use a resumable downloader such as aria2c:
1
2
3
4
5
mkdir -p /content/drive/MyDrive/ComfyUI/models/checkpoints
aria2c -x 16 -s 16 -k 1M \
-d /content/drive/MyDrive/ComfyUI/models/checkpoints \
-o model-name.safetensors \
"https://example.com/path/to/model-name.safetensors"
Do not paste private Hugging Face, Civitai, Google, or tailnet credentials into public notebooks or documentation. If a model requires authentication, enter the token only inside your own private Colab session.
Colab networking with Headscale or Tailscale
Colab usually blocks normal TUN networking and iptables operations. Use Tailscale userspace networking instead:
1
2
3
4
5
tailscaled \
--tun=userspace-networking \
--state=/tmp/tailscaled.state \
--socket=/tmp/tailscaled.sock \
--socks5-server=127.0.0.1:1055
Then join the private tailnet with a preauth key:
1
2
3
4
5
6
tailscale --socket=/tmp/tailscaled.sock up \
--login-server=https://headscale.example.com \
--authkey="$HEADSCALE_AUTHKEY" \
--hostname=colab-comfy-worker \
--accept-dns=false \
--netfilter-mode=off
Prefer short-lived, non-reusable preauth keys. A reusable long-lived key is convenient, but it should be treated like a password and never published in a notebook download, Git repository, blog post, screenshot, or chat log.
Callback proxy for userspace networking
With Tailscale userspace networking, Python code inside Colab may not route directly to the server tailnet IP. A simple local callback proxy can forward worker callbacks through the Tailscale SOCKS listener:
1
2
ncat -lk 127.0.0.1 18188 \
--sh-exec 'ncat --proxy 127.0.0.1:1055 --proxy-type socks5 100.64.0.2 8188'
Then configure the master callback host used by the distributed extension as:
1
2
3
4
5
{
"master": {
"host": "127.0.0.1:18188"
}
}
Use placeholder tailnet addresses in documentation. Replace 100.64.0.2 with the real private address only in your private configuration.
ComfyUI-Distributed workflow rules
A distributed workflow is not just a normal checkpoint workflow. It needs the distributed nodes that tell ComfyUI where to run the job and how to collect the result:
1
2
3
Distributed Model Name → CheckpointLoaderSimple.ckpt_name
Distributed Seed → KSampler.seed
VAE Decode → Distributed Collector → Save Image or Preview Image
Important details:
- Queue the workflow from the self-hosted master UI, not from the Colab worker UI.
- Keep the worker enabled and reachable in the distributed panel.
- Use master delegate-only mode when the server has no GPU or no real local model file.
- The master model dropdown only scans server-side model folders. It does not automatically list Google Drive models mounted on the Colab worker.
- Use the exact worker-side filename in
Distributed Model Name, for examplesd_xl_base_1.0.safetensors. - If the worker finishes but cannot return the image, check
/distributed/job_complete, callback proxy, worker timeout, and whether the master still has the pending distributed job.
For slow first runs, increase distributed worker timeouts. First model load on Colab can take longer than the image sampling itself:
1
2
3
4
5
6
7
{
"settings": {
"worker_timeout_seconds": 600,
"media_sync_timeout_seconds": 600,
"master_delegate_only": true
}
}
GPU and PyTorch checks in Colab
Before starting the worker, confirm the runtime has a GPU and CUDA-enabled PyTorch:
1
nvidia-smi
1
2
3
4
import torch
print(torch.__version__)
print(torch.cuda.is_available())
print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "NO CUDA")
A correct T4 runtime should report CUDA available and a GPU name such as Tesla T4. If PyTorch says +cu128 but torch.cuda.is_available() is False, the runtime is not attached to a GPU. Change the Colab runtime type to a GPU runtime, restart it, and run the setup again.
When this pattern is worth using
Use the Colab-worker pattern when:
- you want occasional GPU bursts;
- models are too large to keep downloading locally;
- Drive persistence is acceptable;
- manual runtime startup is acceptable.
Use a real GPU VM or local GPU host when:
- you need unattended startup;
- you need reliable long sessions;
- you do not want to reconnect workers;
- you need predictable queue latency.
Colab is excellent for experiments, but a dedicated GPU worker is much cleaner for daily production use.
Backup notes
Back up these folders:
1
2
3
4
5
/home/ubuntu/comfyui/data/models
/home/ubuntu/comfyui/data/input
/home/ubuntu/comfyui/data/output
/home/ubuntu/comfyui/data/user
/home/ubuntu/comfyui/data/custom_nodes
The models folder can be huge, so decide whether your backup strategy stores model files or only documents how to re-download them.
The most valuable small folders are usually:
data/userfor UI/workflow state;data/custom_nodesfor installed extensions;- selected workflow JSON files from outputs or exports.
Related documentation
ComfyUI depends on the same homelab foundation as the rest of the stack:
- Deploy Traefik as a Docker reverse proxy with Cloudflare DNS
- Deploy Authentik SSO with Docker and Traefik
- Deploy Cloudflare Companion for automatic Traefik DNS records
- Deploy OpenClaw Gateway with Docker, Traefik, and Authentik
- Deploy LM Studio as a local AI API with Docker
- Deploy Samba root share with Headscale/Tailscale access
Together, these posts form the pattern: route services with Traefik, create DNS automatically, protect sensitive UIs with Authentik, and keep each project isolated in its own folder.
Operational commands
Common commands:
1
2
3
4
5
6
7
cd /home/ubuntu/comfyui
docker compose ps
docker compose logs -f comfyui
docker compose up -d
docker compose up -d --build
docker compose restart comfyui
Update the image after changing the Dockerfile or upstream dependencies:
1
2
3
cd /home/ubuntu/comfyui
docker compose build --no-cache comfyui
docker compose up -d
Clean old build cache only when disk space requires it:
1
docker builder prune
Be careful with broad Docker cleanup commands on multi-service hosts. They can remove images or volumes used by other services.