killBottleneck is in public beta — cloud and self-host.killBottleneck is in beta.Beta on GitHub →
Skip to content

Configuration ​

Configuration lives in .env next to docker-compose.yml, with one exception: SMTP is set in the PocketBase admin UI, not in the file. The complete variable list with defaults is in Environment variables; this page covers the decisions rather than the table.

After any change:

bash
docker compose up -d --build

AI ​

AI is off by default and the app is fully usable that way. Switch it on with KB_AI_PROVIDER:

ProviderWhat it means
noneNo AI. Nothing ever leaves your server. This is the default.
openaiAny OpenAI-compatible API. OpenAI, OpenRouter, Groq, Mistral, Together — or your own vLLM, LM Studio, llama.cpp or liteLLM proxy. Address, key, model name. Dictation works through the same service.
ollamaYour own local model. Install Ollama, ollama pull gpt-oss:20b, then set KB_AI_URL=http://IP:11434 and KB_AI_MODEL=gpt-oss:20b. Everything stays inside your network. Basic prompts; voice transcription is not included.
apiA remote AI service compatible with the killBottleneck API — an address and token from your provider. No GPU of your own and no maintenance.
customYour own endpoint honouring the same API contract — the contract is written down here.
bash
KB_AI_PROVIDER=ollama
KB_AI_URL=http://192.168.1.10:11434
KB_AI_MODEL=gpt-oss:20b

Connecting a provider with an API key (openai) ​

Almost every provider today speaks the OpenAI shape, so one switch covers them all. You need three things: the base address (it usually ends in /v1), your API key, and the exact model name your provider uses.

bash
KB_AI_PROVIDER=openai
KB_AI_URL=https://openrouter.ai/api/v1
KB_AI_TOKEN=sk-...
KB_AI_MODEL=openai/gpt-4o-mini
ServiceKB_AI_URLExample KB_AI_MODEL
OpenAIhttps://api.openai.com/v1gpt-4o-mini
OpenRouterhttps://openrouter.ai/api/v1openai/gpt-4o-mini
Groqhttps://api.groq.com/openai/v1(name from your provider)
vLLM / LM Studio / llama.cpphttp://IP:PORT/v1(the name the server reports)

Write the address down to the version

The address must point at the root of the OpenAI interface. If your service sits on a sub-path (a liteLLM proxy behind https://company.com/llm, say), write it out in full, including the version — https://company.com/llm/v1. From a bare address we cannot tell whether the service lives at the root and wants /v1 appended (that is how OpenAI does it) or sits exactly there and wants nothing appended, so we append /v1 whenever the path carries no version. An address that already contains a version anywhere in its path (…/v1beta/openai) is left alone. Got it wrong? Test connection tells you at once — you will not find out from silence.

You can also set it up in the app instead of .env — Administration → AI features → OpenAI-compatible — and press Test connection, which tells you straight away whether the key works and whether the model name exists.

Which model to pick — measured, not guessed

Measured on 20 Aug 2026 by running all six AI features of killBottleneck (map from a goal, clarifying questions, sub-steps, map from text, chat over a map, morning summary) through the product itself, in Czech. Price is what those six calls actually cost.

ModelPassedMap from a goalSix features cost
gpt-4o-mini ⭐6/611.2 s, 8 nodes$0.0011
gpt-4.1-mini6/66.4 s, 7 nodes$0.0025
gpt-4.1-nano6/62.8 s, 7 nodes$0.0005
gpt-5-mini6/624.5 s, 7 nodes$0.0120
o4-mini6/616.8 s, 7 nodes$0.0329
gpt-5-nano3/6 ❌26.2 s, 7 nodes$0.0044

gpt-4o-mini is the recommended default — it is the one we clicked through end to end, from "Suggest with AI" to a saved map and AI-generated sub-steps. gpt-4.1-nano is cheaper and quicker and passed everything too; gpt-4.1-mini sits in between.

⚠️ Avoid reasoning models (gpt-5-*, o4-*). They are 2–4× slower and 5–30× more expensive, because you also pay for the thinking you never see — o4-mini spent 5,824 of its 7,082 output tokens on reasoning. And gpt-5-nano loses three of the six features outright: it burns the whole budget on thinking and returns nothing. killBottleneck tells you that instead of silently doing nothing, but it is not a model to build on.

The quickest check for any other model is Test connection followed by Suggest a plan from a goal: either a tree appears, or you get an error saying what went wrong.

The key and the money are yours

The key is stored on your server and is never handed back to the browser. Requests are billed by your provider, not by us — killBottleneck sends nothing anywhere on its own.

Dictation goes to the same service (/audio/transcriptions, the whisper-1 model by default — change it with KB_AI_TRANSCRIBE_MODEL). If you would rather run transcription somewhere else, point KB_AI_TRANSCRIBE_URL at it and that address wins.

On a nine-second Czech sentence both whisper-1 (1.9 s) and gpt-4o-mini-transcribe (0.7 s) transcribed every word correctly, diacritics included. The second one is also cheaper ($0.003 vs $0.006 per minute) — but that is one sample, so the default stays whisper-1.

⚠️ "OpenAI-compatible" means the chat, not necessarily transcription. OpenAI has /audio/transcriptions; OpenRouter, vLLM and LM Studio usually do not. killBottleneck therefore asks the service for its model list and only offers dictation when it finds a transcription model there — a microphone that always fails is worse than no microphone. If your service names its model differently (Groq uses whisper-large-v3), set it in Administration → AI features or via KB_AI_TRANSCRIBE_MODEL.

A cap on spending ​

With your own API key every AI call costs you money, so one person can run at most 60 AI operations per hour and 20 dictations per hour by default (KB_AI_MAX_PER_HOUR, KB_AI_MAX_TRANSCRIBE_PER_HOUR). Without a cap a single account — or a stuck browser tab — could burn your credit in a loop. Above the cap the app says so plainly; nothing is lost. The cap applies only to openai: a local Ollama costs nothing and the AI service via API has its own quota.

Which model to pick

gpt-oss:20b is the verified one — killBottleneck is tested against it. Plan for what it needs:

Model size~14 GB
Sensible operationa GPU with 16 GB of memory or more
Answer timeseconds to tens of seconds (~23 s measured for suggesting questions)

We recommend only this one, because it is the only one we have tested. You are free to try another — Ollama offers plenty — but that is at your own risk. It is not about how "smart" the model is: killBottleneck needs a machine-readable answer (JSON), and smaller or so-called reasoning models sometimes return it truncated — the feature then silently does nothing.

Of the ones we measured, gemma3:12b (~8 GB) held up and is usable on a 12 GB card, just slower. Another model of the same size returned a broken answer on the harder task — which is why "at your own risk" is meant literally. The quickest way to check a new model is to try Suggest a plan from a goal: either a tree appears, or nothing happens.

Without a GPU the model runs on the CPU and answers take minutes; that is not enough to work with.

killBottleneck talks to Ollama directly (/api/chat) — nothing sits in between and nothing leaves your network.

Where your data goes

With any provider other than none, map data is sent to the endpoint you configured. With ollama pointing at a machine you own, that is still your network. With api or custom it is whatever you pointed it at. There is no third option hidden anywhere.

A separate model just for daily summaries ​

The one-line encouragement in My day is short formatted text — a smaller, faster model is plenty. KB_SUMMARY_PROVIDER / _URL / _MODEL / _TOKEN override the general AI config for that one job. Left empty, the general configuration is used.

The summary is generated in the morning at KB_SUMMARY_HOUR (default 6) only for accounts that signed in within KB_SUMMARY_ACTIVE_DAYS days (default 14, 0 = everyone); everyone else gets theirs when they open the app.

What the AI is not allowed to do

The AI never enumerates your task lists — those are computed from your data and clickable. Task names are sanitised before they enter the prompt. Turning AI off costs you exactly one line in My day; the panel itself keeps working.

Registration and the registration key ​

The first account to register becomes the administrator. After that, either people register themselves, or an administrator invites them from Administration.

Set KB_SETUP_CODE and every registration requires that key. Hand it to the people you want to let in. An invitation from an administrator does not need the key.

Reachable from the internet?

Set KB_SETUP_CODE before you expose the instance. Empty means open registration.

Sign in with Google ​

Optional — users then sign in with Google instead of e-mail and password.

  1. In the Google Cloud Console: APIs & Services → Credentials → Create credentials → OAuth client ID → Web application.
  2. Under Authorized redirect URIs add: https://YOUR-DOMAIN/api/oauth2-redirect
  3. Put the values in .env:
    bash
    KB_GOOGLE_CLIENT_ID=…apps.googleusercontent.com
    KB_GOOGLE_CLIENT_SECRET=…
  4. docker compose up -d --build — the button appears by itself.

Empty variables = Google sign-in is off and the button is not shown.

E-mail (SMTP) ​

SMTP is configured in the PocketBase admin UI, not in .env:

http://SERVER-IP:8090/_/ → Settings → Mail settings

The superuser account is created on first start; the link is in docker compose logs.

What changes if you skip SMTP ​

E-mail is off by default and the instance is fully usable that way. Only these differ:

With SMTPWithout SMTP
Inviting a membere-mailed with a linkthe admin is shown a temporary password to hand over
"Forgot your password?"self-service, link arrives by e-mailnot offered — the admin resets it (see below)
Reminders and daily digestdelivered per each person's preferencesin-app bell only

The password-reset link is hidden on purpose without SMTP: the server would confirm the request, no message would arrive, and the person would wait for mail that does not exist.

Forgotten password without SMTP ​

For a team member, the admin resets it: Organisation settings → key icon next to the person → confirm. A new temporary password appears to hand over; the old password stops working immediately and the person is signed out everywhere. They are told in the bell that someone reset it.

For the admin, nobody can do this — there is no one above them (and the app does not offer resetting your own password either). Here the two versions differ:

  • Hosted version (killbottleneck.com): e-mail always works, so the admin uses the normal "Forgot your password?" on the sign-in page. Nothing else to handle.
  • Your own server without SMTP: the route goes through the PocketBase console, which you have access to as the operator:
  1. http://SERVER-IP:8090/_/ → sign in as superuser
  2. users collection → find your account → Change password

No superuser yet? Create one on the server (service name comes from docker-compose.yml):

bash
docker compose exec killbottleneck /app/pocketbase superuser upsert you@example.com 'YourStrongPassword'

Watch out

The console on :8090/_/ runs over plain HTTP — open it only from your local network or through an SSH tunnel, never over the internet. A password typed on the command line also stays in your shell history and is briefly visible to other users of the server; change it in the console afterwards and delete the history entry.

Recommendation

Create a second account with the admin role and keep its password in a password manager. The PocketBase route works, but it is extra work in the middle of a day when nobody can sign in.

Notification e-mails are off by default and controlled by each user's preferences. If e-mails were already reaching your users before an upgrade and you want to keep them, KB_NOTIFY_EMAIL_DEFAULT=1.

Time zone and recurring templates ​

TZ applies to the whole instance, not per user. It decides which zone "every Monday" and "at 5 in the morning" are counted in. A team spread across zones gets the server's zone.

If the server was powered off during KB_AUTO_HOUR, recurring projects are created at the next later hour the same day — they are not silently skipped.

Attachment limits ​

KB_FILES_MB caps uploaded files across the whole instance:

  • empty — no limit; the self-hosting default, it is your disk
  • a number — cap in MB (5000 = 5 GB)
  • 0 — uploading disabled; attachments can still be added as links, which take no space and always point at the current version of the file

Why 0 is the default on our hosted version

It is not about saving disk space, it is deliberate: we do not store other people's files. They never enter our backups, they do not turn into "we process customer documents" in a contract, and they stay where they already are. On your own server it is your disk and your call.

Automations ​

Only relevant if you drive goals from an automation. KB_PUBLIC_URL is the address the agent should call back on — see Environment variables → Automations for why leaving it empty can make a remote agent call itself, and why KB_ALLOW_PRIVATE_WEBHOOKS is off by default.

Instance mode ​

Leave KB_HOSTED empty on your own server. It exists for instances that run at a provider alongside other customers, where the instance administrator must not be able to point the AI service into a private network and probe the neighbours.

fair-code — self-hosting and internal use are free, reselling as a hosted service is not.