Until today, every article on this blog had an aesthetic problem: no cover image. PaperMod shows covers on the home page, in lists, and at the top of each post — without an image, the layout loses half of its impact. Drawing them by hand every time, though, was the quickest way to stop publishing anything.
So I added to my .NET + Angular CMS a feature that generates the covers on its own, using an image-generation model that already runs on my GX10: ComfyUI with Qwen Image 2512.
The flow, in a nutshell
From the admin panel, every post now has a Generate cover button. Here is what happens:
- The API (a
CoversSlicein ASP.NET Core) receivesPOST /api/covers/{slug}and immediately replies202 Accepted. - In the background it builds the JSON workflow for ComfyUI and submits it to port 8188.
- The UI polls
GET /api/covers/{slug}/statusuntil it seesdone. - The image is saved to
static/images/cover-{slug}.webp(WebP, ~10x smaller than PNG; the lossless text-free master stays incovers-master/) and the post’s front matter is updated withcover.image(for PaperMod) andimages(for theog:imagetags when the post is shared). - The Hugo rebuild kicks in, and the cover appears on the site.
The workflow: quantized GGUF + Turbo LoRA
The workflow reuses the configuration I already use for generating images locally on the GX10 (128 GB of unified RAM helps, but it is not infinite):
- UnetLoaderGGUF with
qwen-image-2512-Q5_K_M.gguf— the diffusion model at Q5 quantization, loaded into VRAM/RAM in a few seconds; - Turbo LoRA at 2–4 steps with CFG 1: instead of the classic 30+ steps, generation completes in a handful of seconds;
- CLIP
qwen_2.5_vl_7b_fp8_scaledas the text encoder and the dedicated VAE for Qwen Image.
The dimensions are 1200×672: a 16:9 multiple of 16 (an architectural constraint of Qwen Image) very close to the 1200×630 recommended for og:image.
The RAM constraint that designed the architecture
The most interesting design choice is the child of a limitation: on the GX10, llama-server running Qwen text and Qwen Image cannot comfortably fit together in unified RAM. Gaining thirty seconds of prompt enrichment from the LLM would have cost the risk of multi-minute swaps during generation.
So the endpoint never calls the LLM to write the prompt: the default is a template derived from the post’s title, description, tags, and categories. From the interface, though, the prompt can always be overridden manually — and that is the route I prefer, because the best images come from prompts written (or at least tweaked) by me.
A container calling the host
One last detail to sort out: the API runs in a Docker container, ComfyUI runs on the host as a user systemd service. Classic solution: COMFY_URL=http://host.docker.internal:8188 + extra_hosts: host-gateway in the compose file. In local development (Aspire, no container) the same variable points to localhost:8188 and the code does not change.
This post is the proof
The cover you are looking at on top of this article was not drawn by me, nor was its prompt hand-written: the flow described above generated it with the automatic template, in about thirty seconds, while I was writing the last paragraph. It is officially a post that took its own photo. 📸
