From 5b61caa19741c5d13317a901a2c9d8c20db4192d Mon Sep 17 00:00:00 2001 From: William Gill Date: Sun, 19 Apr 2026 10:40:41 -0500 Subject: [PATCH] =?UTF-8?q?Add=20deployments/ai-stack=20=E2=80=94=20combin?= =?UTF-8?q?ed=20production-shape=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sanitized snapshot of the live srvno.de stack: Caddy + Ollama (with preseed) + ComfyUI + Open WebUI + Anubis stub. Real hostnames, secrets, and bcrypt hash replaced with placeholders so the dir is safe to commit. Caddyfile updated to point at comfyui:8188 (the source file pointed at the now-removed forge service). Dropped FIGMENT_/FORGE_/SEGMENT_IMAGE_TAG from the env example. Harmonised the init-models.sh mount path between ollama and model-init services. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 9 ++ deployments/ai-stack/.env.example | 16 ++ deployments/ai-stack/Caddyfile | 25 ++++ deployments/ai-stack/README.md | 59 ++++++++ deployments/ai-stack/docker-compose.yml | 187 ++++++++++++++++++++++++ deployments/ai-stack/init-models.sh | 12 ++ 6 files changed, 308 insertions(+) create mode 100644 deployments/ai-stack/.env.example create mode 100644 deployments/ai-stack/Caddyfile create mode 100644 deployments/ai-stack/README.md create mode 100644 deployments/ai-stack/docker-compose.yml create mode 100644 deployments/ai-stack/init-models.sh diff --git a/README.md b/README.md index 4c431e4..cf38530 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,15 @@ Use `docker compose build` to rebuild locally when iterating on the Dockerfile. Then drop a checkpoint into the `comfyui-models` volume and finish the Open WebUI ComfyUI configuration. Full walkthrough in [SETUP.md](SETUP.md). +## Deployment examples + +- **Repo root** (`docker-compose.yml`) — minimal `comfyui` + `open-webui` + bridge for kicking the tyres on a single-purpose box. +- **[`deployments/ai-stack/`](deployments/ai-stack/)** — production-shaped + multi-service stack: Caddy (TLS + reverse proxy), Ollama (with one-shot + model preseeding), ComfyUI, Open WebUI, optional Anubis anti-bot. Mirrors + the live `srvno.de` deployment. + ## Replaces This repo supersedes the previous figment + segment + Forge stack. ComfyUI's diff --git a/deployments/ai-stack/.env.example b/deployments/ai-stack/.env.example new file mode 100644 index 0000000..0f12c4e --- /dev/null +++ b/deployments/ai-stack/.env.example @@ -0,0 +1,16 @@ +# Copy to .env and fill in. docker compose picks .env up automatically. +# Never commit the populated .env — it's in .gitignore at repo root. + +# Cookie-signing key for Open WebUI sessions. Generate once with: +# openssl rand -hex 32 +# Must stay stable across restarts — rotating it logs every user out. +WEBUI_SECRET_KEY=replace-with-32-byte-hex + +# Ed25519 private key (hex) for the Anubis PoW sidecar. Generate with: +# openssl rand -hex 32 +# Only needed if you uncomment the anubis-owui service in docker-compose.yml. +ANUBIS_OWUI_KEY=replace-with-32-byte-hex + +# ComfyUI image tag to deploy. `latest` tracks whatever the release workflow +# last pushed; pin to a v* tag (e.g. 0.1.0) for reproducible deploys. +COMFYUI_IMAGE_TAG=latest diff --git a/deployments/ai-stack/Caddyfile b/deployments/ai-stack/Caddyfile new file mode 100644 index 0000000..595a9f7 --- /dev/null +++ b/deployments/ai-stack/Caddyfile @@ -0,0 +1,25 @@ +{ + email you@example.com +} + +# ─── chat.example.com — Open WebUI ────────────────────────────────────────── +# Swap `open-webui:8080` for `anubis-owui:8923` once the Anubis sidecar in +# docker-compose.yml is uncommented. +chat.example.com { + reverse_proxy open-webui:8080 +} + +# ─── comfyui.example.com — ComfyUI native web UI ──────────────────────────── +# Open WebUI submits jobs internally over the bridge — this hostname is only +# for direct access to ComfyUI's own UI (workflow editing, custom-node +# install via ComfyUI-Manager, etc.). Basic auth keeps it private; remove +# the block if you don't need to expose it externally at all. +comfyui.example.com { + basic_auth { + # Generate with: + # docker run --rm caddy:latest caddy hash-password --plaintext 'your-password' + comfyui REPLACE_WITH_BCRYPT_HASH + } + + reverse_proxy comfyui:8188 +} diff --git a/deployments/ai-stack/README.md b/deployments/ai-stack/README.md new file mode 100644 index 0000000..f354285 --- /dev/null +++ b/deployments/ai-stack/README.md @@ -0,0 +1,59 @@ +# ai-stack — combined deployment example + +The full multi-service stack: Caddy (TLS + reverse proxy) in front of Open +WebUI (chat + image generation panel), Ollama (LLMs), and ComfyUI (image +generation), with an optional Anubis PoW anti-bot sidecar. One GPU host, +one bridge network, one TLS entry point. + +This is what runs in production for the `srvno.de` deployment, sanitized +for sharing. + +## Files + +| File | Purpose | +| ------------------- | -------------------------------------------------------- | +| `docker-compose.yml`| Service definitions, volumes, GPU reservations | +| `Caddyfile` | TLS + reverse proxy config (one site block per hostname) | +| `init-models.sh` | Models to preseed into Ollama on first boot | +| `.env.example` | Secrets and image-tag pins. Copy to `.env` | + +## Bring it up + +```sh +cp .env.example .env +# edit .env — generate the two keys with `openssl rand -hex 32` + +# edit Caddyfile — replace example.com hostnames with yours, replace the +# basic_auth bcrypt placeholder, point DNS at this host + +# edit init-models.sh — keep the models you want, drop the rest + +docker compose up -d +docker compose logs -f +``` + +First boot: Caddy provisions Let's Encrypt certs (DNS must already point +here), the model-init container pulls the LLMs listed in `init-models.sh` +(can take a while — `mistral-nemo:12b` is ~7 GB), and ComfyUI initialises +its volumes empty. Drop a checkpoint into the `comfyui-models` volume +under `checkpoints/` before the first image generation — see the repo +root [SETUP.md](../../SETUP.md) §4. + +After Open WebUI is up, finish the ComfyUI integration via the admin +panel — paste the four workflow JSONs from [`../../workflows/`](../../workflows/). +See [SETUP.md §6](../../SETUP.md). + +## What's different from the upstream `comfyui-nvidia` standalone compose + +The standalone compose at the repo root is just `comfyui` + `open-webui` +on a fresh bridge — meant for kicking the tyres or running ComfyUI on a +single-purpose box. This deployment adds: + +- **Caddy** with auto-TLS, so Open WebUI and ComfyUI are reachable on + real hostnames over HTTPS instead of `localhost:3000`. +- **Ollama** for chat models, with a one-shot `model-init` puller that + preseeds whatever `init-models.sh` lists. +- **Anubis** anti-bot stub for protected hostnames (commented in by + default; uncomment + provide a key to activate). +- No host ports published for ComfyUI or Open WebUI — Caddy is the only + service bound to 80/443. diff --git a/deployments/ai-stack/docker-compose.yml b/deployments/ai-stack/docker-compose.yml new file mode 100644 index 0000000..fecc3f7 --- /dev/null +++ b/deployments/ai-stack/docker-compose.yml @@ -0,0 +1,187 @@ +# ai-stack — combined deployment example: caddy + comfyui + ollama + +# open-webui (+ anubis later). One GPU host, one bridge network, one TLS +# entry point. +# +# This is the "full meal" deployment. The minimal "just ComfyUI" example +# lives in the repo root (../../docker-compose.yml). + +name: ai-stack + +volumes: + comfyui-models: + comfyui-custom-nodes: + comfyui-input: + comfyui-output: + comfyui-user: + ollama-data: + open-webui-data: + caddy-data: + caddy-config: + +services: + # --------------------------------------------------------------------------- + # Caddy — the only service exposed on 80/443. Terminates TLS (auto Let's + # Encrypt), reverse-proxies to the in-compose services by name. + # --------------------------------------------------------------------------- + caddy: + image: caddy:2-alpine + container_name: caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + - "443:443/udp" # HTTP/3 + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + - caddy-config:/config + depends_on: + - open-webui + - comfyui + healthcheck: + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:2019/config/"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + # --------------------------------------------------------------------------- + # Ollama — LLM daemon, GPU-backed. + # --------------------------------------------------------------------------- + ollama: + image: ollama/ollama:latest + container_name: ollama + restart: unless-stopped + # 11434 only published if you want direct access from the VM host. + # Services inside the stack reach it via http://ollama:11434. + ports: + - "11434:11434" + volumes: + - ollama-data:/root/.ollama + environment: + - OLLAMA_HOST=0.0.0.0:11434 + - OLLAMA_KEEP_ALIVE=30m + - OLLAMA_MAX_LOADED_MODELS=2 + - OLLAMA_FLASH_ATTENTION=1 + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + healthcheck: + test: ["CMD-SHELL", "ollama list >/dev/null 2>&1 || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + + # One-shot model puller. Runs after ollama is healthy, pulls whatever + # init-models.sh lists, exits. `restart: "no"` keeps it from looping. + model-init: + image: ollama/ollama:latest + container_name: ollama-model-init + depends_on: + ollama: + condition: service_healthy + volumes: + - ollama-data:/root/.ollama + - ./init-models.sh:/init-models.sh:ro + environment: + - OLLAMA_HOST=ollama:11434 + entrypoint: ["/bin/sh", "/init-models.sh"] + restart: "no" + + # --------------------------------------------------------------------------- + # ComfyUI — image generation (txt2img + img2img), GPU-backed. + # No host port published — Caddy fronts it. Uncomment 8188 only for + # VM-local debug or to use ComfyUI's native web UI directly. + # + # Replaces the figment + segment + Forge trio. Open WebUI talks to + # /prompt directly; SAM2/Grounding-DINO masking lives in custom_nodes + # (install via ComfyUI-Manager) instead of as a separate sidecar. + # --------------------------------------------------------------------------- + comfyui: + image: git.anomalous.dev/alphacentri/comfyui-nvidia:${COMFYUI_IMAGE_TAG:-latest} + pull_policy: always + container_name: comfyui + restart: unless-stopped + # ports: + # - "8188:8188" + volumes: + - comfyui-models:/opt/comfyui/models + - comfyui-custom-nodes:/opt/comfyui/custom_nodes + - comfyui-input:/opt/comfyui/input + - comfyui-output:/opt/comfyui/output + - comfyui-user:/opt/comfyui/user + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:8188/system_stats"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s + + # --------------------------------------------------------------------------- + # Open WebUI — multi-user chat. + # --------------------------------------------------------------------------- + open-webui: + image: ghcr.io/open-webui/open-webui:main + container_name: open-webui + restart: unless-stopped + # ports: not published; Caddy fronts it + environment: + ENABLE_OPENAI_API: "false" + WEBUI_AUTH: "true" + ENABLE_SIGNUP: "false" + DEFAULT_USER_ROLE: "pending" + WEBUI_URL: "https://chat.example.com" + WEBUI_SECRET_KEY: "${WEBUI_SECRET_KEY}" + OLLAMA_BASE_URL: "http://ollama:11434" + ENABLE_IMAGE_GENERATION: "true" + IMAGE_GENERATION_ENGINE: "comfyui" + COMFYUI_BASE_URL: "http://comfyui:8188" + IMAGE_SIZE: "1024x1024" + IMAGE_STEPS: "45" + # Workflow JSON + node mappings are pasted into the admin panel + # (Settings → Images). Source files live in this repo under + # workflows/ — txt2img.json, txt2img.nodes.json, img2img.json, + # img2img.nodes.json. + volumes: + - open-webui-data:/app/backend/data + depends_on: + ollama: + condition: service_healthy + comfyui: + condition: service_started + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 60s + + # --------------------------------------------------------------------------- + # Anubis — PoW anti-bot sidecar(s). One per protected hostname. To enable: + # uncomment the block, generate a key with `openssl rand -hex 32`, and flip + # the corresponding `reverse_proxy` target in Caddyfile from + # `open-webui:8080` → `anubis-owui:8923`. + # --------------------------------------------------------------------------- + anubis-owui: + image: ghcr.io/techarohq/anubis:latest + container_name: anubis-owui + restart: unless-stopped + environment: + BIND: ":8923" + TARGET: "http://open-webui:8080" + DIFFICULTY: "4" # SHA-256 leading zeros; 4 = ~1s client work + COOKIE_DOMAIN: "chat.example.com" + METRICS_BIND: ":9090" + ED25519_PRIVATE_KEY_HEX: "${ANUBIS_OWUI_KEY}" diff --git a/deployments/ai-stack/init-models.sh b/deployments/ai-stack/init-models.sh new file mode 100644 index 0000000..1b3fd0f --- /dev/null +++ b/deployments/ai-stack/init-models.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Preseed Ollama with the models the stack should have available at startup. +# Runs once via the model-init service (see docker-compose.yml). Safe to +# re-run — `ollama pull` is a no-op for models already present. +# +# Add or remove pulls to taste. The host needs enough disk for everything +# listed here; check sizes at https://ollama.com/library before adding. + +set -e + +ollama pull mistral-nemo:12b +ollama pull llama3.2:3b