Open WebUI as a Self-Hosted Chat Front End
10 min read · updated August 11, 2026
Open WebUI is the front end and nothing else: it holds conversations, users, documents and settings, and it delegates every token to a server you point it at. That separation is the reason to use it and the source of both problems people hit.
Run it
The documented container form, from the project’s README:
docker run -d -p 3000:8080 \ --add-host=host.docker.internal:host-gateway \ -v open-webui:/app/backend/data \ --name open-webui --restart always \ ghcr.io/open-webui/open-webui:main
Three parts of that matter. The port mapping puts the UI on 3000 on your machine while the app listens on 8080 inside the container. The volume at /app/backend/data is where every conversation, account and setting lives — omit it and a container recreation deletes all of it, which the documentation warns about explicitly. And --add-host=host.docker.internal:host-gateway is what lets the container reach a server running on the host, which is almost certainly where your model is.
There is a pip install as well — pip install open-webui then open-webui serve, listening on 8080, with Python 3.11 called out as the supported version. There is also an :ollama image tag that bundles Ollama in the same container, and a --gpus=all variant of it. Take the bundled image only if you want one thing to manage; take the plain image if you already have a server, which is the case this page assumes.
Point it at your model server
Two connection families exist and they are configured separately. Ollama has its own, defaulting to http://localhost:11434 via OLLAMA_BASE_URL. Everything else goes through the OpenAI-compatible path, which is what LocalAI, llamafile, llama-server and LM Studio all present.
In the UI that is Settings → Connections. By environment variable it is OPENAI_API_BASE_URLS and OPENAI_API_KEYS — note the plurals, which take several values separated by semicolons and pair up positionally, so a missing key still needs its slot. The singular OPENAI_API_BASE_URL exists for the one-backend case and defaults to OpenAI’s own endpoint. ENABLE_OPENAI_API defaults to true; setting it false empties the model list, which is a confusing symptom if you did not do it deliberately.
docker run -d -p 3000:8080 \ --add-host=host.docker.internal:host-gateway \ -e OPENAI_API_BASE_URLS="http://host.docker.internal:8080/v1;http://host.docker.internal:1234/v1" \ -e OPENAI_API_KEYS="sk-local;sk-local" \ -v open-webui:/app/backend/data \ --name open-webui ghcr.io/open-webui/open-webui:main
Use host.docker.internal rather than localhost in those URLs. Inside a container, localhost is the container, so a perfectly working server on your machine is simply not there. This is the most common connection failure and it produces an empty model list rather than an error.
The account model
The first account created gets administrator privileges and controls user management. Subsequent sign-ups land in a pending state and need an admin to approve them — consistent with DEFAULT_USER_ROLE defaulting to pending and ENABLE_SIGNUP defaulting to true in the environment reference.
The practical consequence is that the window between starting the container and creating your own account is the window in which somebody else can become your administrator. If the port is reachable by anyone else, create the first account immediately, before you do anything else.
There is also a single-user mode, WEBUI_AUTH=False, which removes login entirely. The quick start is explicit that this is one-way: you cannot switch between single-user mode and multi-account mode after the change. Treat it as a decision about the deployment rather than a convenience toggle, and only on something that is not reachable from a network.
Why your environment variable stopped working
This one costs people an afternoon. Many of Open WebUI’s settings are marked ConfigVar in the project’s environment configuration reference, and the documentation states that on first launch every environment variable is used, but that ConfigVar values are then persisted internally — after the initial launch, restarting the container no longer uses the external environment values.
So editing OPENAI_API_BASE_URLS in your compose file and restarting appears to do nothing, because the database already holds the answer and the database wins. There are two correct responses: change it in the UI, which is what the persisted store is for, or set ENABLE_PERSISTENT_CONFIG=False so the environment always wins. The second is right for a declaratively managed deployment; its cost, which the documentation names, is that changes made in the UI no longer survive a restart. Pick one and be consistent, because the confusing state is having half your configuration in each place.
Before you put it on a network
- Create the admin account before the port is reachable by anyone else.
- Set
ENABLE_SIGNUPto false once your real users exist, so an open port does not accumulate pending accounts. - Put TLS in front of it. Open WebUI serves plain HTTP; a reverse proxy is the normal answer and passwords over plain HTTP on a shared network are not.
- Remember the backend. Open WebUI can be locked down while the model server it points at is still an unauthenticated listener on the same host. Bind that one to loopback and let only the container reach it.
- Back up the volume. Every conversation and account lives in
/app/backend/data, and nothing else does.
ConfigVar where older documentation said PersistentConfig, and the auth variables have been reorganised. Check the current reference against the image tag you are running.