- A complete setup: Open WebUI, Postgres with pgvector for document search, and automatic HTTPS, in one Docker Compose file you can copy.
- The setting most guides miss: Open WebUI resets its chat address before document search, voice and images read it, so each one needs its own address or it goes to OpenAI.
- What we learned running it: why the slim image refuses PDFs, why one message is several model calls, and why changing an address in the settings file later does nothing.
This is a walkthrough for running Open WebUI on a server you control, connected to an OpenAI-compatible API rather than to models on the same machine. By the end you will have Open WebUI on your own domain with HTTPS, chats and document search in Postgres, and a way to back it up and update it.
We host Open WebUI for customers, so we have a view on this, and we say where at the end. Everything before that applies wherever you run it.
Three ways to give a team AI chat
- A chat product with seats, such as ChatGPT Business. Nothing to run, a price per person, and the models that company offers.
- Open WebUI on your own server. Your chats stay with you, there is no seat price, and you choose the models. You do the setup, updates and backups. This guide is about this option.
- Managed hosting. Someone else runs the server, and you still get your own instance and database. That is what we sell, at the end of the post.
What you need
- A Linux server you can reach over SSH, with Docker and the Compose plugin, and a few gigabytes of free disk for the image. 2 GB of memory is plenty to start: Open WebUI used between half and three-quarters of a gigabyte on our servers, and its database less than a tenth of one.
- A domain or subdomain you control, such as
chat.example.com, with anArecord pointing at the server's IP address. - Ports 80 and 443 open to the internet, so HTTPS certificates can be issued.
- An API key for an OpenAI-compatible service, and its base URL.
Step 1: the settings file
Make a folder and create a file called .env in it. Everything secret or specific to
you lives here:
mkdir ~/open-webui && cd ~/open-webui
cat > .env <<EOF
WEBUI_HOST=chat.example.com
POSTGRES_PASSWORD=$(openssl rand -hex 24)
WEBUI_SECRET_KEY=$(openssl rand -hex 32)
OPENAI_API_BASE_URL=https://api.openai.com/v1
OPENAI_API_KEY=your-api-key
EOF
chmod 600 .env
Change WEBUI_HOST to your domain, and the last two lines to your provider's base URL
and key. WEBUI_SECRET_KEY signs people's sessions. Keep it stable: if it changes,
everyone is signed out, though nothing else is lost.
Step 2: the Compose file
Create docker-compose.yml beside it:
services:
postgres:
image: pgvector/pgvector:pg16
restart: unless-stopped
environment:
POSTGRES_USER: openwebui
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: openwebui
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openwebui -d openwebui"]
interval: 10s
retries: 5
open-webui:
image: ghcr.io/open-webui/open-webui:v0.12.3
restart: unless-stopped
environment:
DATABASE_URL: postgresql://openwebui:${POSTGRES_PASSWORD}@postgres:5432/openwebui
VECTOR_DB: pgvector
WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY}
WEBUI_URL: https://${WEBUI_HOST}
ENABLE_OLLAMA_API: "false"
# Chat
OPENAI_API_BASE_URL: ${OPENAI_API_BASE_URL}
OPENAI_API_KEY: ${OPENAI_API_KEY}
# Document search
RAG_EMBEDDING_ENGINE: openai
RAG_EMBEDDING_MODEL: text-embedding-3-small
RAG_OPENAI_API_BASE_URL: ${OPENAI_API_BASE_URL}
RAG_OPENAI_API_KEY: ${OPENAI_API_KEY}
# Voice input and read-aloud
AUDIO_STT_ENGINE: openai
AUDIO_STT_MODEL: whisper-1
AUDIO_STT_OPENAI_API_BASE_URL: ${OPENAI_API_BASE_URL}
AUDIO_STT_OPENAI_API_KEY: ${OPENAI_API_KEY}
AUDIO_TTS_ENGINE: openai
AUDIO_TTS_MODEL: tts-1
AUDIO_TTS_OPENAI_API_BASE_URL: ${OPENAI_API_BASE_URL}
AUDIO_TTS_OPENAI_API_KEY: ${OPENAI_API_KEY}
# Images, if you turn them on later
IMAGES_OPENAI_API_BASE_URL: ${OPENAI_API_BASE_URL}
IMAGES_OPENAI_API_KEY: ${OPENAI_API_KEY}
volumes:
- webui_data:/app/backend/data
depends_on:
postgres:
condition: service_healthy
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
volumes:
db_data:
webui_data:
caddy_data:
What each part is doing:
postgreskeeps users, chats and settings, and thepgvectorimage adds the extension document search needs. Open WebUI creates the extension itself on first start. WithoutDATABASE_URL, everything goes into a small file in the data folder.- The full image, not
-slim. The slim image is 0.2 GB to download instead of 1.6 GB, but it can only read text, Markdown, CSV, HTML and code files. PDF, Word, Excel and PowerPoint, which are what people actually upload, need the full image or a separate document extractor. VECTOR_DB: pgvectorputs document search in your Postgres. The full image otherwise keeps it in a folder of its own.ENABLE_OLLAMA_API: "false"stops Open WebUI looking for a local model server that is not there.- The four groups of addresses are the part to get right, and the next section explains why each one is spelled out.
- The model names are OpenAI's. If your provider names its embedding, speech or voice models differently, use its names.
Step 3: HTTPS
Create a file called Caddyfile with your domain in it:
chat.example.com {
reverse_proxy open-webui:8080
}
Then start everything:
docker compose up -d
docker compose logs -f open-webui
Open https://chat.example.com and create the first account. The first account
becomes the admin, so create yours straight away: until someone signs up, the first
person to open the address takes that role. Everyone who signs up after that
waits as "pending" until the admin approves them in the Admin Panel, under Users.
Why every address is set by name
This is the mistake that does not show up on day one. Chat works, so the setup looks finished, and then the first uploaded document or voice message goes somewhere you did not intend.
In Open WebUI 0.12.3, OPENAI_API_BASE_URL is read once for chat. A few lines later in
its configuration, the same name is set back to https://api.openai.com/v1. The
settings for document search, voice input, read-aloud and images are read after that,
and each one defaults to that name. So if you set only the chat address:
- chat goes to your provider, as you asked;
- document search, voice and images go to OpenAI, carrying your provider's key.
Usually that fails with an authentication error the first time someone uploads a file. If you also happen to have an OpenAI key in the setup, it works, and those requests go to a service you did not choose. Setting each feature's own address, as in the Compose file above, closes both cases.
Changing a setting later
On first start, Open WebUI copies these settings into its database and reads them from
there afterwards. Change an address in .env and restart, and nothing happens. Make
later changes in the Admin Panel, under Settings, or set ENABLE_PERSISTENT_CONFIG to
false if you want the settings file to stay in charge.
Step 4: backups
Two things hold your data: the database, and the data folder, where uploaded files are kept. Back up both, and copy them off the server:
docker compose exec -T postgres pg_dump -U openwebui openwebui | gzip > ~/owui-db-$(date +%F).sql.gz
docker run --rm -v open-webui_webui_data:/data -v ~/:/backup alpine \
tar czf /backup/owui-files-$(date +%F).tar.gz -C /data .
The volume name starts with your folder's name; docker volume ls shows it. A backup
that only lives on the machine it is protecting goes down with that machine.
Step 5: updating
Read the release notes, take a backup, change the tag in docker-compose.yml, then run:
docker compose pull open-webui
docker compose up -d open-webui
Open WebUI updates its database as it starts, so run one copy at a time: never two
containers against the same database. If something checks whether it is up, point it at
/ready, which answers only after startup has finished and the database has answered.
What we learned running it
- Start with the full image. We started on slim for its size and switched when it refused a PDF. The full image took under three minutes to download on our servers; later updates reuse most of it.
- It is light. Between half and three-quarters of a gigabyte of memory for Open WebUI, and under a tenth of one for its database, on our servers.
- One message is several calls. Besides the answer, Open WebUI asks the chat's model for a title, tags and follow-up suggestions, so a message can make three or four calls. Count that in when you estimate what the models will cost.
- Its branding stays. Open WebUI's license does not allow its name or logo to be changed for deployments with more than 50 users, unless you have an enterprise license from them.
Or skip the server
If the steps above are more upkeep than you want, we run this setup for you. Open WebUI hosting on Roar AI launches it from your console with every model we serve already connected, for chat, document search, voice input and read-aloud. Its database, document search and file storage are set up, and it runs in Colombo, Bangalore or Singapore. An Australian team's Open WebUI runs in Singapore for now (why).
It has limits worth knowing before you choose it. It keeps its own name and logo, as above. And because Open WebUI keeps its settings in its database, if your app's key is ever replaced, you paste the new one into its admin settings yourself.
Open WebUI is made by Open WebUI Inc. Roar AI hosts Open WebUI and is not affiliated with or endorsed by Open WebUI Inc.