One place on the server where small language models run, for any stack that needs one. It speaks the OpenAI chat API, so any OpenAI-compatible client works with it. It is reachable only from the
interstackdocker network, not from the internet.
http://llm:8080 on interstackPOST http://llm:8080/v1/chat/completions
Authorization: Bearer <key>
{"model": "qwen3.5-0.8b", "messages": [...], "stream": true}
Every request names its model: there is no default, a request without model gets a 400. GET /v1/models lists the models and whether each is loaded; GET /health needs no key.
| Model | model |
Used by |
|---|---|---|
| Qwen3.5 0.8B | qwen3.5-0.8b |
SERMO's Lapis-ACP in the Teletype BBS, and any stack: a general small model, good at English and JSON, Hungarian only roughly; thinking off; loaded at start |
| Gemma 3 270M instruct | gemma-3-270m |
Nobody now (SERMO's model until October 2026): the fastest, but it leaves a persona at any question beyond its examples; loaded on its first request |
The server is llama.cpp's llama-server in router mode: it starts a llama-server per model on demand. The models are the sections of server/models.ini in the repository, baked into the service's image; a section's name is the model of a request, and its keys are llama-server's long options (hf-repo, c, parallel, ...). The GGUF is downloaded from Hugging Face on the first load and kept in a volume.
load-on-startup = true is loaded when the service starts (Qwen3.5, so SERMO never waits). The others load on their first request.Adding a model is a new section in server/models.ini and a push: the deploy builds a new image and restarts the service with it.
Router mode can also load and unload models over HTTP, so the service asks for a key. LLM_API_KEYS in the stack's .env holds the accepted keys, one per caller, so one caller can be cut off alone.
| Caller | Its key |
|---|---|
| Teletype BBS | SERMO_LLM_API_KEY in the teletype-bbs stack's .env |
A new caller gets a new key (openssl rand -hex 24), appended to LLM_API_KEYS.
The stack lives in /srv/stacks/llm, which is not a git checkout. A push to master that changes compose.yaml or server/ deploys it through Woodpecker, the same way as the FTP server: the pipeline only calls the server over SSH with its own key, bound to one fixed command, ci-deploy llm (in infra/host-config). The server clones master itself, checks the compose file against its policy (no extra privileges, no docker socket, no public port), copies compose.yaml and server/ in and runs docker compose up -d --build. .env and the model volume stay. The pipeline gets no host volumes.