Run Ollama as its own pinned service, not a binary lifted from its image - #3
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/ollamaout ofollama/ollama:latest; modern Ollama keeps theinference engine in
/usr/lib/ollama(llama-server,libggml, the CUDAbackends).
/api/tagsanswered — so the health check passed — while everygeneration 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/ollama:0.30.10rather thanscavenged from
latest. Bumping the tag is now a deliberate act.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.ymlis a separate overlay, so a host without a usable GPUrefuses to start instead of quietly falling back to CPU.
by the API over the compose network.
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
~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
pytest31/31, run in apython:3.12-slimcontainer.docker compose logs ollama | grep "inference compute"→library=CUDA.Pairs with the matching change in
forail-helm.