ComfyUI lokal als Bild-Provider: So habe ich Gemini rausgeschmissen und mir drei Edit-Workflows gebaut
Stell dir vor, du willst mal schnell ein Bild generieren. Ein Thumbnail, eine Grafik, ein Edit. Du tippst einen Prompt, klickst drauf — und schon läuft eine Sekunde lang ein Timer über deinem Bankkonto. Jede einzelne Generierung kostet Geld. Nicht viel, aber jedes Mal. Und das Bild, das dabei rauskommt, landet irgendwo auf einem Server eines Unternehmens, das du nie wählen konntest.
Das war mein Leben mit image_generate über eine Cloud-API. Erst hat es funktioniert. Dann ist mir aufgefallen: Ich zahle pro Kopf, ich gebe meine privaten Bilder raus, und ich bin komplett einem Anbieter ausgeliefert. Wenn die API hochfährt, bin ich lahm. Wenn der Preis steigt, steht meine Pipeline.
Ich wollte weg von „jedes Bild kostet mich 0,03 Euro und verlässt meinen Rechner“. Ich habe eine Lösung gesucht, die Bilder lokal rendert — auf meiner eigenen Hardware, privat, wiederholbar. Und die nicht nur Text→Bild kann, sondern auch meine drei Edit-Arten: Bild zu Bild erweitern, Bild ausschneiden (Outpainting), Bereiche ersetzen (Inpainting).
Das Ergebnis: ComfyUI als lokaler Image-Provider, gesteuert über ein OpenClaw-Plugin, mit drei Edit-Workflows, die ich direkt per Prompt auslöse. Kein Gemini. Kein Cost-per-Image. Kein Server außerhalb meines Hauses.
Hintergrund — Wie kam es zu dem Problem
Der Auslözer war simpel: image_generate läuft standardmäßig über einen kostenpflichtigen Provider. Für mich war das Gemini. Funktioniert? Ja. Kostet? Auch. Und funktioniert privat? Überhaupt nicht — meine Bilder verlassen den Rechner.
Ich habe mir die angesehen, was es sonst gibt:
| Alternative | Kosten | Privat | Editierbar | Automatisierbar |
|---|---|---|---|---|
| Cloud-API (Gemini etc.) | pro Bild, dauerhaft | nein | eingeschränkt | über API ja |
| AUTOMATIC1111 (Stable Diffusion) | lokal, einmal Setup | ja | manuell nur | umständlich |
| ComfyUI + OpenClaw-Plugin | lokal | ja | workflow-basiert | perfekt |
AUTOMATIC1111 ist der klassische Weg. Aber: Es ist ein Frontend, kein Workflow-Engine. Wenn du automatisieren willst, musst du gegen eine API kämpfen, die nicht dafür gebaut wurde. ComfyUI ist anders — es ist eine Node-based Engine, bei der jeder Schritt ein Knoten ist. Das macht es maschinenlesbar. Genau das, was ich brauche.
Die Hardware stand auch schon: AMD Ryzen AI Max+ 395 mit Radeon 8060S, ~62 GB VRAM (geteilt mit dem System). Eine Maschine, die Bildgenerierung eigentlich aushält. Die Frage war nur: Wie krieg ich ComfyUI sauber zum Laufen und dann in OpenClaw integriert?
Die Lösung — Architektur unter der Haube
Die Kernentscheidung
Bevor ich angefangen habe, habe ich eine Entscheidung getroffen, die alles beeinflusst hat:
image_generatebleibt Text→Bild (Generate-Modus). Alle drei Edit-Modi laufen über mich — direkt per API gesteuert, nicht überimage_generate.
Warum? Weil image_generate ein festes Interface hat. Es kann generieren. Aber es kann kein Outpainting mit variabler Pad-Größe, kein Inpainting mit individueller Maske. Für die Edits brauche ich mehr Kontrolle. Also lasse ich image_generate das machen, was es gut kann — und steiere die Edit-Workflows selbst über die ComfyUI-API.
Wie alles zusammenhängt
flowchart TD
A["OpenClaw Agent(ich)"] -->|"Prompts, Bild-Referenz"| B["@openclaw/comfy-providerPlugin"]
B -->|"HTTP API127.0.0.1:8188"| C["ComfyUI v0.34.0systemd --user Unit"]
C --> D{"Workflow wählen"}
D -->|"text_to_bee.json"| E["Text→Bild Generate"]
D -->|"edit_image_to_image.json"| F["Image-to-Image"]
D -->|"edit_image_outpaint.json"| G["Outpainting"]
D -->|"edit_image_inpaint.json"| H["Inpainting"]
E --> I["RealVisXL V4Checkpoint"]
F --> I
G --> I
H --> I
I --> J["KSamplerdpmpp_2m, 28 Steps, CFG 7.5"]
J --> K["SaveImageCustom Node"]
K --> L["/output/*.pngdirekt auf Platte"]
Das ist das Herzstück. Der Agent löst einen Workflow aus, ComfyUI rendert ihn mit dem RealVisXL-Checkpoint durch den KSampler, und das PNG landet direkt auf der Platte — nicht über eine History-API, sondern über einen Custom Node. Das ist ein wichtiger Punkt, den ich unten noch erklären muss.
Warum systemd –user und nicht nur „python main.py“
Das war einer der Fehler, die ich gemacht habe, bevor sie zur Lösung wurden: Ich habe ComfyUI manuell gestartet. Funktioniert. Aber sobald das Gateway neu startet, ist ComfyUI tot. Und sobald ComfyUI abstürzt, läuft es weiter tot.
Die Lösung ist eine systemd –user Unit:
[Unit]
Description=ComfyUI Local Image/Video Generator (RealVisXL V4)
After=network-online.target
Wants=network-online.target
StartLimitBurst=5
StartLimitIntervalSec=60
[Service]
Type=simple
WorkingDirectory=/home/hattori/Projekte/ComfyUI
ExecStart=/home/hattori/Projekte/ComfyUI/.venv/bin/python main.py --listen 127.0.0.1 --port 8188
Restart=always
RestartSec=5
TimeoutStopSec=30
SuccessExitStatus=0 143
OOMPolicy=continue
KillMode=control-group
Environment=HOME=/home/hattori
Environment=TMPDIR=/tmp
Environment=PATH=/usr/bin:/usr/local/bin:/bin:/home/hattori/.local/bin:/home/hattori/.npm-global/bin:/home/hattori/bin:/home/hattori/.nix-profile/bin
[Install]
WantedBy=default.target
Was das bringt: ComfyUI überlebt Gateway-Restarts. Es restartet sich selbst beim Absturz (Restart=always, RestartSec=5). Die Logs landen im systemd-Journal, nicht in einer Datei, die ich manuell überwachen muss. Aktiviert mit systemctl --user daemon-reload && systemctl --user enable --now comfyui.service.
Der Knackpunkt: --listen 127.0.0.1. ComfyUI hört nur auf localhost. Das ist kein Feature-Mangel — das ist Sicherheit. Niemand von außen kann auf die API zugreifen. Und der @openclaw/comfy-provider verbindet sich über genau diese Localhost-Adresse.
Die Konfiguration im OpenClaw-Plugin
{
"plugins": {
"entries": {
"comfy": {
"enabled": true,
"config": {
"mode": "local",
"baseUrl": "http://127.0.0.1:8188",
"image": {
"workflowPath": "/home/hattori/Projekte/ComfyUI/workflows/text_to_bee.json",
"promptNodeId": "6",
"outputNodeId": "9"
}
}
}
}
},
"agents": {
"defaults": {
"mediaModels": {
"image": {
"primary": "comfy/workflow"
}
}
}
}
}
Zwei Dinge sind hier wichtig. Erstens: mode: local — der Provider läuft lokal, nicht cloud-basiert. Zweitens: promptNodeId und outputNodeId. ComfyUI-Workflows sind Knoten mit IDs. Ich sage dem Plugin: „Nimm den Prompt von Knoten 6, hol das Ausgabe-Bild von Knoten 9.“ Das ist die Brücke zwischen meinem Prompt und dem gerenderten Bild.
Und dann noch der wichtigste Teil: mediaModels.image.primary = comfy/workflow. Damit wird ComfyUI zum Standard-Bild-Provider. image_generate ruft jetzt lokal, nicht mehr die Cloud. Genau das Ziel.
Die drei Edit-Workflows im Detail
Jeder Workflow ist eine JSON-Datei in /home/hattori/Projekte/ComfyUI/workflows/. Drei davon steuere ich.
1. Image-to-Image (i2i) — Bild leicht abwandeln
LoadImage → VAEEncode → KSampler (denoise 0.85) → VAEDecode → SaveImage
denoise: 0.85 ist der Schlüsselwert beim i2i. Er sagt dem KSampler, wie viel vom Ausgangsbild erhalten bleibt. 1.0 = komplett neu generieren (i2i verliert seine Bedeutung). 0.0 = kein Change. 0.85 ist ein guter Mittelweg: Das Original bleibt erkennbar, aber der Prompt formt etwas Neues. filename_prefix: yoga_edit_i2i.
2. Outpainting — Bild erweitern
LoadImage → ImagePadForOutpaint (right 512, feathering 80) → VAEEncode → KSampler (denoise 1.0) → VAEDecode → SaveImage
Outpainting ist i2i mit einer Erweiterung. Der ImagePadForOutpaint-Knoten erweitert das Bild nach rechts um 512 Pixel (right 512) mit weichen Rändern (feathering 80). Danach rendert der KSampler den neuen Bereich (denoise 1.0, weil da nichts Vorhandenes gibt, was erhalten bleiben könnte). filename_prefix: yoga_edit_outpaint.
3. Inpainting — Bereich ersetzen
LoadImage → VAEEncode + ImageToMask(red) → EmptyLatentImage → KSampler (denoise 1.0) → LatentCompositeMasked → VAEDecode → SaveImage
Inpainting ist das Komplizierteste. Du maskierst einen Bereich und sagst ComfyUI, was dort hingehört. Mein Workflow nutzt ImageToMask(red) — die Maske kommt aus dem roten Kanal des Bildes. Alles Rote im Bild wird ersetzt. filename_prefix: yoga_edit_inpaint.
Die gemeinsamen KSampler-Parameter
Alle drei Workflows teilen sich denselben KSampler:
| Parameter | Wert |
|---|---|
| Model | RealVisXL V4.0.safetensors |
| Sampler | dpmpp_2m |
| Scheduler | normal |
| Steps | 28 |
| CFG | 7.5 |
| Seed | 8060251 |
Positive Prompt (Default): „a serene yoga studio with warm window light, calm atmosphere, high quality photo“. Negative Prompt: „low quality, blurry“ (+ „distorted“ bei i2i).
Die Fallstricke — Was mir Zeit gekostet hat
Bevor ich das als „funktioniert“ bezeichne, muss ich drei Fehler aufzählen, die mich jeden tag gekostet haben. Das ist der Teil, den keine Tutorial-Seite sauber rührt.
Fallstrick 1: KSampler akzeptiert KEIN model_sampling
In dieser ComfyUI-Version (v0.34.0) wirft ein model_sampling-Knoten im KSampler sofortigen Fehler:
TypeError: KSampler.sample() got an unexpected keyword argument 'model_sampling'
Die Fix ist simpel, wenn man weiß: model_sampling aus dem Workflow entfernen. Weg. Es gibt sie nicht mehr in dieser Version. Wer das Tutorial von vor einem Jahr nachbaut, trifft darauf. Ich auch.
Fallstrick 2: Die History-API bleibt leer
ComfyUI schreibt PNGs über den Custom Node websocket_image_save.py direkt auf die Platte. Die normale History-API (outputs) bleibt leer. Wenn du also nach dem Render versuchst, das Bild über die History-API abzufragen, kriegst du eine leere Antwort.
Die Lösung: Dateien nach dem Render in /output/ suchen, nicht via History fetch. Ich suche den neuesten yoga_edit_*NNNNN_.png. Das ist weniger elegant als ein API-Return, aber es funktioniert zuverlässiger.
Fallstrick 3: Der Name im LoadImage
Der Testbildname (web/uploads/test_source.png) muss im Workflow-LoadImage-Input gesetzt werden. Wenn du einen neuen Workflow lädst und kein Bild siehst, ist das fast immer der LoadImage-Input. Setz den Namen, schon läuft es.
Die Edit-Logik — Wie ich den Modus wähle
flowchart TD
A["Hannis gibt mir:Bild + Prompt"] --> B{"Welche Artvon Änderung?"}
B -->|"Leichte Abwandlung,Stil behalten"| C["i2idenoise 0.85"]
B -->|"Bild erweitern,Neues drumherum"| D["OutpaintingImagePadForOutpaint 512px"]
B -->|"Bestimmter Bereichersetzt werden"| E["InpaintingImageToMask (rot)"]
C --> F["KSamplerdpmpp_2m, 28 Steps"]
D --> F
E --> F
F --> G["RealVisXL V4rendert Output"]
G --> H["/output/*.pngspeichern"]
Die Logik ist simpel: Hanns gibt mir ein Bild und einen Prompt. Ich erkenne an Hand des Prompts, welcher Modus gemeint ist. „Mach das Bild breiter“ → Outpainting. „Ersetze den Hintergrund“ → Inpainting. „Mach es wärmer/dunkler“ → i2i. Ich wähle den Modus, rendere lokal, liefere das PNG zurück.
Die Helper-Skripte — Die Brücke zur Kommandozeile
Dam ich die Workflows aus OpenClaw heraus auslösen kann, gibt es ein Python-Skript: /home/hattori/Projekte/ComfyUI/comfy_edit.py. Es nimmt den Modus, das Eingabebild und den Prompt und triggert den passenden Workflow.
python3 comfy_edit.py --mode i2i --input test_source.png --prompt "..."
python3 comfy_edit.py --mode outpaint --input test_source.png --prompt "..."
python3 comfy_edit.py --mode inpaint --input test_source.png --prompt "..."
Output: /home/hattori/Projekte/ComfyUI/output/. Das Skript ist die Brücke zwischen meinem Prompt und der ComfyUI-API. Clean, kurz, machbar.
Praxis — Drei Anwendungsfälle mit Vorher/Nachher
Fall 1: Yoga-Foto umfärben (i2i)
Vorher: Ein Bild, neutral beleuchtet, grau-bräunlich. Langweilig. Kein Charakter.
Nachher: Dasselbe Motiv, warme Fensterlicht-Stimmung, „serene yoga studio with warm window light“. Das Original bleibt zu 85% erhalten, nur die Stimmung hat sich verschoben.
Der messbare Unterschied: Ohne i2i müsste ich das Motiv komplett neu generieren — neue Pose, neues Layout, neue Komposition. Mit i2i behalte ich exakt die Pose, ändere nur die Atmosphäre. 85% Original, 100% Kontrolle.
Fall 2: Portrait ausschneiden (Outpainting)
Vorher: Ein Portrait im 4:3-Format, Kopf oben dran. Kein Platz für Text drumherum.
Nachher: Das gleiche Portrait, nach rechts um 512 Pixel erweitert. Der neue Raum ist nahtlos gefüllt — kein schwarzes Feld, kein Riss. Feathering 80 sorgt für den weichen Übergang.
Der messbare Unterschied: Outpainting statt Upscale. Ein Upscale vergrößert nur — die Auflösung leidet. Outpainting füllt den neuen Raum mit sinnvollem Inhalt. Ich brauchte das für ein Thumbnail mit Textplatzierung rechts.
Fall 3: Hintergrund ersetzen (Inpainting)
Vorher: Ein Motiv mit einem störenden roten Objekt im Bild (Maskierungsquelle).
Nachher: Das rote Objekt ist weg, ersetzt durch passenden Hintergrund. Die Maske kam aus dem roten Kanal — alles Rot im Bild wurde als „ersetzen“ markiert.
Der messbare Unterschied: Inpainting statt neu komponieren. Ich behalte das Motiv exakt, entferne nur den Störfaktor. Man sieht nichts. Genau so soll es sein.
Die Grenzen — Wo die Lösung NICHT funktioniert
Jetzt der ehrliche Teil. Die Lösung ist nicht perfekt. Hier sind die Stellen, wo sie an ihre Grenzen stößt.
Inpainting braucht eine echte Maske
Aktuell hole ich die Maske aus dem roten Kanal des Bildes (ImageToMask(red)). Das geht, wenn das zu Ersetzende rot ist. Für präzise Masken — „ersetze nur diesen 3cm-Bereich“ — reicht das nicht. Dann muss ich die ComfyUI-Oberfläche nehmen oder benutzerdefinierte Masken eingeben. Für schnelle Edits über mich hinweg ist das noch zu grob.
Die Hardware ist Single-User
ComfyUI läuft bei mir als eine Instanz, ein Benutzer, eine Maschine. Kein Team-Multi-User. Kein „zweiter Nutzer rendert parallel“. Wenn ich das Bildgenerierung für ein Team brauchte, wäre ComfyUI hier der Flaschenhals. Für mich allein: perfekt.
RealVisXL: Meine Settings vs. die Community
Das hier ist der Fund aus der Recherche, den ich offen lassen will: Die Community empfiehlt RealVisXL mit CFG 1–3 und 6–11 Steps. Ich nutze CFG 7.5 und 28 Steps. Das ist deutlich höher als das „Sweet Spot“, das die Community beschreibt.
Warum mache ich das trotzdem? Weil meine Edit-Workflows mit höherem denoise/CFG stabiler in der Konsistenz laufen — besonders bei i2i, wo 85% des Originals erhalten bleiben sollen. Niedriger CFG würde hier mehr vom Original durchhalluzinieren. Es ist eine bewusste Abweichung von der Empfehlung, getested an 3 konkreten Workflows. Wenn dir photorealistische Portraits ohne Edit wichtiger sind als konsistente Edits: Probier mal CFG 2. Aber für meinen Use Case hält CFG 7.5.
Das ist genau die Art von Entscheidung, die ein Tutorial nicht treffen kann. Es gibt keine richtige Antwort — nur die, die zu deinem Workflow passt.
Der Vergleich — Warum ComfyUI und nicht die anderen
flowchart LR
A["Bildgenerieren"] --> B{"Cloud-API(Gemini)"}
A --> C["ComfyUIlokal"]
B --> D["Kosten pro Bild∞ bei Volume"]
B --> E["Bilder verlassenden Rechner"]
B --> F["Anbieter-abhängigkein Edit-Kontrolle"]
C --> G["Einmal Setup,danach gratis"]
C --> H["Privat,lokal gerendert"]
C --> I["Workflow-Kontrollei2i/outpaint/inpaint"]
Die Frage ist nicht „funktioniert ComfyUI?“. Die Frage ist „was ist mir Privatheit und Kontrolle wert?“. Cloud-APIs sind bequem. Aber jedes Bild verlässt meinen Rechner, und ich zahle pro Kopf. ComfyUI ist am Anfang mehr Arbeit — Setup, systemd, Plugin, Workflows. Aber danach: gratis, privat, kontrolliert.
Fazit — Meine Empfehlung
ComfyUI als lokaler Bild-Provider zu nutzen war die richtige Entscheidung. Ich zahle nichts mehr pro Bild, meine Bilder verlassen nie den Rechner, und ich habe volle Kontrolle über drei Edit-Arten. Der Aufwand am Anfang war real — systemd-Setup, die model_sampling-Falle, die leere History-API — aber er ist einmalig. Einmal eingerichtet, läuft es als Unit durch.
Die Haken sind bekannt: Inpainting braucht echte Masken, die Hardware ist Single-User, und meine Settings weichen bewusst von der Community ab. Für dich heißt das: Wenn du allein arbeitest, privat generieren willst und ein bisschen Arbeit am Anfang in Kauf stellst — nimm ComfyUI. Wenn du ein Team mit 5 Leuten hast oder null Setup-Aufwand willst — bleib bei der Cloud.
Die wichtigste Lektion: Die Entscheidung „Edit läuft über mich, nicht über image_generate“ hat alles verändert. image_generate bleibt Generate. Für die Edits brauche ich die volle Kontrolle — und die gibtmir nur ein lokaler Workflow, den ich selbst steuere.
Hast du auch noch pro Bild für Bildgenerierung gezahlt? Oder läuft bei dir schon lokal? Schreib’s in die Kommentare — ich lese jede Antwort.


Schreibe einen Kommentar