Skip to content

Run Ollama as its own pinned service, not a binary lifted from its image - #3

Merged
krlex merged 10 commits into
developfrom
feat/split-ollama-service
Aug 19, 2026
Merged

Run Ollama as its own pinned service, not a binary lifted from its image#3
krlex merged 10 commits into
developfrom
feat/split-ollama-service

Conversation

@krlex

@krlex krlex commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Ollama leaves the assistant image and runs as its own pinned service, with an
optional GPU.

Why

The all-in-one image could start a model server but never load a model. It
copied only /bin/ollama out of ollama/ollama:latest; modern Ollama keeps the
inference engine in /usr/lib/ollama (llama-server, libggml, the CUDA
backends). /api/tags answered — so the health check passed — while every
generation returned HTTP 500. Reproduced against a rebuilt replica of the
Dockerfile and against the published ghcr.io/forail-platform/forail-assistant:2026.06.0.

What changed

  • Ollama runs as its own service, pinned to ollama/ollama:0.30.10 rather than
    scavenged from latest. Bumping the tag is now a deliberate act.
  • The API waits for the model server and pulls models over Ollama's HTTP API —
    the CLI is no longer in this image. The wait is bounded at 300s and exits with
    the URL it was trying to reach instead of hanging.
  • docker-compose.gpu.yml is a separate overlay, so a host without a usable GPU
    refuses to start instead of quietly falling back to CPU.
  • Ollama gets no published port: it has no authentication and is reachable only
    by the API over the compose network.
  • Documentation the split invalidated: architecture, configuration, deployment
    and disaster recovery all described a single container. The recovery procedure
    in particular would have backed up the index and silently skipped several GB
    of model blobs — the one thing an air-gapped host cannot re-fetch.

Measured — RTX 3080 + Ryzen 9 5900X, gemma3:1b, warm

Time to first token Generation throughput
CPU, 24 threads ~0.5s ~680 B/s
GPU ~0.5s ~3900 B/s

~5–6× on generation. Time to first token barely moves — that is RAG retrieval,
not the model. The GPU does not improve precision; its value is making a larger
model affordable.

Verified

  • pytest 31/31, run in a python:3.12-slim container.
  • Stack brought up on GPU and confirmed with
    docker compose logs ollama | grep "inference compute"library=CUDA.

Pairs with the matching change in forail-helm.

krlex added 10 commits July 28, 2026 20:40
…ts image

The image copied /bin/ollama out of ollama/ollama:latest and nothing else.
That silently stopped working: Ollama keeps its inference engine in
/usr/lib/ollama (llama-server, libggml, the CUDA backends), so the copied
binary could start a server and answer /api/tags -- enough for the health
check to look fine -- while every generation failed with

    error starting llama-server: llama-server binary not found

Reproduced against the published 2026.06.0 image, which carries Ollama 0.30.8
without that directory. It has never been able to load a model.

Rather than chase upstream's internal file layout, Ollama now runs as its own
service:

- Pinned to ollama/ollama:0.30.10. Tracking `latest` is precisely what let an
  upstream change break inference without a line of our code changing.
- Only the model server benefits from a GPU, so only it has to carry that
  requirement -- in Kubernetes just that pod needs a GPU node.
- No published port. Ollama has no authentication, so it stays reachable only
  from the API, and the integration overlay keeps it off the Forail network.

The API now waits for Ollama on startup and pulls models over its HTTP API,
since the ollama CLI is no longer in the image. The wait is bounded at 300s
and exits with an actionable message instead of hanging forever.
Kept as an overlay instead of folded into docker-compose.yml because a devices
reservation is a hard requirement: on a host without a GPU the stack refuses to
start rather than quietly running on CPU. Opting in is explicit:

    docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d

Ollama falls back to CPU silently when it cannot see a device, so the file
documents how to confirm it actually took: `logs ollama | grep "inference
compute"` must report a CUDA library and non-zero VRAM, not library=cpu.

Measured on a Ryzen 9 5900X / RTX 3080 12GB with gemma3:1b, warm, same
questions against the same index: ~680 B/s on 24 CPU threads against
~3900 B/s on the GPU. Time to first token barely moves (~0.5s either way) --
that is RAG retrieval, not generation.
test_default_settings built a Settings() and checked its values, but
pydantic-settings reads FORAIL_ASSISTANT_* out of the environment first. The
shipped image sets those (it points the API at the ollama service rather than
localhost), so running the suite inside that image failed on a correct default.

Clear the prefix from the environment before constructing Settings, so the test
means what its name says wherever it runs.
The README still promised a single all-in-one container with Ollama bundled
inside, which is no longer true and was the arrangement that could not run a
model in the first place.

- New architecture diagram: API (FastAPI + embedded ChromaDB) alongside Ollama,
  with why they are apart -- GPU requirement on the model server alone, and no
  published port because Ollama has no authentication.
- Hardware section replaced with numbers actually measured here rather than
  estimates, plus the command to confirm Ollama took the GPU plus the toolkit
  prerequisite. It falls back to CPU silently, so "check, do not assume".
- FORAIL_ASSISTANT_RAG_TOP_K documented as 3, which is what the code has always
  defaulted to; the table said 5.
Records the failure mode explicitly -- health checks passing while every
generation returned 500 -- because that combination is what kept it hidden, and
because the published 2026.06.0 image is affected.
The diagram and the component notes still said FastAPI, Ollama and ChromaDB
share a container and that the entrypoint starts Ollama itself. None of that
has been true since the split. Also records why the copied-binary layout was
abandoned, so the pinned upstream image does not look like an arbitrary
preference.
The base-URL row explained localhost as "runs inside the same container",
which is exactly what stopped being true, and the model-change recipe told the
reader to exec the ollama CLI in the API container — it is not in that image
any more, so the command fails. Point both at the model server.
Four things in here were not just stale but actively wrong after the split: the
service list, the GPU section (it told the reader to uncomment a block that no
longer exists, on the wrong service), teardown that stops the API and leaves
the model server up, and a backup procedure that claimed one volume holds both
the index and the models.
The document opened by saying all persistent state lives in one place; since
the split there are two claims, and following it would have backed up the index
while silently skipping several GB of model blobs — the one thing an air-gapped
host cannot re-fetch. Adds the unreachable-model-server case, which is now a
distinct failure with its own message.
This file is part of docs_to_index/, so the assistant answers from it. Left
alone it would have kept telling users the model server ships inside the
application image.
@krlex
krlex merged commit 81d06cc into develop Aug 19, 2026
2 checks passed
@krlex
krlex deleted the feat/split-ollama-service branch August 19, 2026 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant