Open WebUI with an OpenAI-compatible endpoint
Open WebUI is a self-hosted chat interface that teams put in front of models. It connects to OpenAI-compatible endpoints through environment variables, supports several endpoints at once, and — this is the part that catches everyone — stores those values as persistent config, so changing the environment later does nothing. This page covers all three, plus what a shared deployment costs per person.
One endpoint: two environment variables
For a single endpoint set OPENAI_API_BASE_URL and OPENAI_API_KEY. The base URL ends in /v1; Open WebUI appends the rest of the path itself.
# docker-compose.yaml — one endpoint, values read from an .env file
# sitting next to this one (and kept out of git).
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
ports:
- "3000:8080"
environment:
- 'OPENAI_API_BASE_URL=${GATEWAY_BASE_URL}' # https://aicomp.ai/v1
- 'OPENAI_API_KEY=${GATEWAY_API_KEY}'
volumes:
- open-webui:/app/backend/data
volumes:
open-webui:
Keeping the values in an .env file beside the compose file rather than inline means the compose file is safe to commit and the key is not. That matters more here than in a single-user tool, because these deployments usually end up in a shared repository.
Several endpoints: the lists pair by position
The plural variables, OPENAI_API_BASE_URLS and OPENAI_API_KEYS, are both semicolon-separated lists. They are matched by position — the first URL is used with the first key, the second with the second. There is no key-to-URL mapping beyond order, so a missing separator silently pairs every subsequent entry with the wrong key.
# Multiple endpoints: semicolon-separated, paired BY POSITION.
# The first URL uses the first key, the second URL the second key.
export OPENAI_API_BASE_URLS="https://aicomp.ai/v1;http://localhost:11434/v1"
export OPENAI_API_KEYS="sk-your-key;unused-for-local"
Local models typically ignore the key, but the slot still has to exist. A placeholder is fine; an empty slot is not, because it shifts the alignment for everything after it.
The trap: these values are persistent
OPENAI_API_BASE_URL and OPENAI_API_KEY are persistent config values. The first time Open WebUI starts, it writes them into its database. After that, changing the environment variable has no effect — the stored value wins. People restart containers for an hour wondering why the new endpoint is not being used.
To change an endpoint after the first run, change it in the admin connections UI, or reset the stored value. The environment variables are authoritative once, at first start.
Model list comes from the endpoint
The model dropdown is populated from /v1/models. An empty dropdown is a connection problem, not a missing-models problem — worth checking directly instead of reloading the interface.
# The model dropdown is populated from /v1/models. If it is empty,
# ask the endpoint directly rather than reloading the UI.
curl "$GATEWAY_BASE_URL/models" \
-H "Authorization: Bearer $GATEWAY_API_KEY"
# Empty or 401 here means the UI can never populate either.
What a shared deployment costs
Self-hosting spreads one endpoint across a team, so the useful number is cost per person rather than cost per request. The table below assumes a five-person team at thirty chats each per working day, nine thousand input tokens and fourteen hundred output tokens per chat.
| Model | Vendor | Rate in / out per 1M | Per chat | Team / month |
|---|---|---|---|---|
| gpt-5.6-luna | OpenAI | $0.1 / $0.6 | $0.0017 | $5 |
| MiniMax-M3 | MiniMax | $0.15 / $0.6 | $0.0022 | $7 |
| deepseek-v4-flash | DeepSeek | $0.22 / $0.66 | $0.0029 | $9 |
| gemini-3.7-flash | $0.375 / $1.875 | $0.0060 | $18 | |
| claude-haiku-4-5-20251001 | Anthropic | $0.5 / $2.5 | $0.0080 | $24 |
| deepseek-v4-pro | DeepSeek | $0.66 / $1.98 | $0.0087 | $26 |
| glm-5.3 | Zhipu | $0.7 / $2.2 | $0.0094 | $28 |
| qwen3.8-max | Alibaba | $1 / $3 | $0.0132 | $40 |
| claude-sonnet-5 | Anthropic | $1 / $5 | $0.0160 | $48 |
| gpt-5.6-terra | OpenAI | $1 / $6 | $0.0174 | $52 |
| kimi-k3 | Moonshot | $1.5 / $7.5 | $0.0240 | $72 |
| claude-opus-5 | Anthropic | $2.5 / $12.5 | $0.0400 | $120 |
How this fails in practice
| What you see | What it usually is | Fix |
|---|---|---|
| Changing the env var does nothing | These are persistent config values written to the database on first start | Change the endpoint in the admin connections UI, or reset the stored value |
| Wrong key used with a URL | URL and key lists are paired by position; a separator is missing or extra | Count the semicolons in both variables — they must produce the same number of entries |
| Model dropdown stays empty | Endpoint unreachable, or /v1/models rejects the key | Query /v1/models directly with the same key |
| 404 from the endpoint | Base URL missing /v1 | Set OPENAI_API_BASE_URL to a URL ending in /v1 |
| Container cannot reach a host service | localhost inside the container is the container, not the host | Use host.docker.internal on Docker Desktop, or the host gateway address on Linux |
| Key committed to the repository | Values written inline in a committed compose file | Keep them in .env and reference by name; add .env to .gitignore |
https://aicomp.ai/v1).
Create one free →
FAQ
Why does changing the environment variable not change the endpoint?
Because OPENAI_API_BASE_URL and OPENAI_API_KEY are persistent config. Open WebUI writes them to its database on first start and reads from there afterwards, so the environment is only consulted once.
How do I connect more than one endpoint?
Use OPENAI_API_BASE_URLS and OPENAI_API_KEYS, both semicolon-separated. They pair by position, so the first URL uses the first key and so on — keep the two lists the same length.
Does the base URL need /v1?
Yes. Open WebUI appends the rest of the request path itself, so the base URL you provide ends in /v1.
Why is my model list empty?
The list is fetched from /v1/models. If that call fails the dropdown is empty — which is a connection or credentials problem rather than a missing model. Query the endpoint directly to confirm.
Can Open WebUI reach a model running on the same machine?
Yes, but not via localhost from inside a container — that resolves to the container. Use host.docker.internal on Docker Desktop, or the host gateway address on Linux.
How do I keep the API key out of version control?
Put it in an .env file beside the compose file, reference it by name, and add .env to .gitignore. The compose file itself is then safe to commit.