Chronicler
Self-hosting

Performance and memory

Give the stack more RAM, and know which knob actually changes anything.

Chronicler is slow for one of three reasons, in this order of likelihood:

  1. There's no GPU, or there is one and it isn't being used.
  2. Docker doesn't have enough memory, so the model reloads constantly.
  3. The model is too big for the machine and is spilling to disk.

Fix them in that order. GPU acceleration covers the first, this page covers the rest.

1. Give Docker more memory

This is the setting people mean when they say "allocate more RAM", and it lives in Docker, not in Chronicler. Nothing in the compose file can raise it.

Docker Desktop with the WSL2 backend takes memory through WSL, and by default it will take up to half the machine. To set it explicitly, create or edit %UserProfile%\.wslconfig:

.wslconfig
[wsl2]
memory=16GB
processors=8
swap=8GB

Then, in an admin PowerShell:

PowerShell
wsl --shutdown

Restart Docker Desktop. Leave the host at least 4–8 GB — starving Windows to feed the model makes everything worse.

wsl --shutdown stops your containers. Do it when nobody is using the server.

Check what Docker actually got:

Terminal
docker info --format "{{.MemTotal}}"

Under 4 GiB and the backend will be killed as soon as a model loads.

2. Tune the model runtime

These go in the .env next to your compose file. Each is optional — leave it out and Ollama's own default applies. Run docker compose up -d after a change.

SettingDefaultWhat it does
OLLAMA_KEEP_ALIVE5mHow long the model stays loaded after a question.
OLLAMA_NUM_PARALLELautomaticHow many questions one model answers at once.
OLLAMA_MAX_LOADED_MODELSautomaticHow many different models stay in memory.
OLLAMA_CONTEXT_LENGTH16384Default context for clients that don't ask for one.

OLLAMA_KEEP_ALIVE — the one worth changing

Ollama unloads the model five minutes after the last question. The next question pays the full cold load again: one to three minutes on a CPU, several seconds on a GPU. On a dedicated server there's no reason to keep paying that.

.env
OLLAMA_KEEP_ALIVE=-1

-1 keeps the model resident permanently. The cost is exactly the model's memory (3–20 GB depending on which one), held whether anyone is asking or not. On a shared machine that has other work to do, prefer something like 2h.

OLLAMA_NUM_PARALLEL — for several people at once

Each parallel slot gets its own context memory, on top of the model weights. Doubling it roughly doubles the per-context memory. With one or two users, leave it alone; a spare 1 also frees memory on a small machine.

.env
OLLAMA_NUM_PARALLEL=4

Only useful with memory to spare. If the machine is already tight, more parallel slots make everything slower, not faster.

OLLAMA_MAX_LOADED_MODELS

Chronicler generates with a single model, so leaving this alone is right for almost everyone. Setting it to 1 on a small machine guarantees a second model never sneaks into memory.

OLLAMA_CONTEXT_LENGTH

Raising this does not give chat a bigger context window. The Chronicler backend asks for its own context size on every request, and that value is fixed in the image. This setting only affects other clients talking to the same Ollama.

Included for completeness. Changing it is almost never the fix you're looking for.

3. Tune the database

Also in .env. The defaults are Postgres' own, which are famously conservative.

SettingDefaultWhat it does
POSTGRES_SHARED_BUFFERS128MBDatabase cache. The one that matters for search across a big library.
POSTGRES_WORK_MEM4MBMemory per sort. Raise in small steps.
.env
POSTGRES_SHARED_BUFFERS=2GB
POSTGRES_WORK_MEM=32MB

A common rule of thumb for shared_buffers is 25% of the memory you're willing to give the database. work_mem is allocated per sort, not per connection, so a large value times many concurrent queries is how servers run out of memory — 32 MB is generous, 1 GB is a mistake.

Only worth touching once you have thousands of documents. Below that, the default is fine and the model is your bottleneck.

4. Right-size the model

A model that doesn't fit is the worst case: it spills to disk and each answer takes minutes. Match the model to the memory you actually have — see Models. Smaller and resident beats bigger and swapping, every time.

Worked examples

Laptop, no GPU, 16 GB

Docker: 8 GB. Model: gemma4:e2b.

OLLAMA_KEEP_ALIVE=30m
OLLAMA_NUM_PARALLEL=1

Office server, no GPU, 32 GB

Docker: 24 GB. Model: gemma4:e4b.

OLLAMA_KEEP_ALIVE=-1
OLLAMA_NUM_PARALLEL=2
POSTGRES_SHARED_BUFFERS=2GB
POSTGRES_WORK_MEM=16MB

Workstation, 24 GB NVIDIA

COMPOSE_PROFILES=nvidia. Model: gemma4:26b.

OLLAMA_KEEP_ALIVE=-1
OLLAMA_NUM_PARALLEL=4
POSTGRES_SHARED_BUFFERS=4GB
POSTGRES_WORK_MEM=32MB

Check what you changed

Terminal
docker compose config | grep -A6 "OLLAMA_"        # what compose resolved
docker exec chronicler-ollama env | grep OLLAMA   # what the container actually got
docker exec chronicler-postgres psql -U chronicler -d chronicler_app -c 'show shared_buffers;'
docker stats --no-stream                          # what everything is using now

A setting you left out of .env won't appear in the container at all. That's correct — it means the runtime's own default is in force.

Speed that isn't about memory

  • Scope your questions. A conversation pointed at three documents is far faster than one pointed at everything.
  • Index during quiet hours. Bulk uploads, especially scanned ones, compete with chat for the same CPU.
  • Use an SSD. Loading a 20 GB model off a spinning disk is a bad minute.