Add deployments/ai-stack — combined production-shape example

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) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 10:40:41 -05:00
co-authored by Claude Opus 4.7
parent 09fcec7c07
commit 5b61caa197
6 changed files with 308 additions and 0 deletions
+9
View File
@@ -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
+16
View File
@@ -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
+25
View File
@@ -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
}
+59
View File
@@ -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.
+187
View File
@@ -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}"
+12
View File
@@ -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