Open WebUI stores users, chats, settings, prompts, API keys, and feature state in its application database. The default local database is convenient for a single container, but shared or scaled deployments need PostgreSQL so writes and migrations are handled by a database server instead of a file inside the data volume.
The database backend is selected when Open WebUI starts. Set DATABASE_URL to a PostgreSQL connection string before the application container is recreated, keep WEBUI_SECRET_KEY stable, and point every replica at the same database. A fresh PostgreSQL database is initialized through the normal migration path during startup.
Docker Compose keeps the database service, application service, persistent volumes, and environment values in one project. The Compose layout starts from an empty PostgreSQL database; an existing SQLite deployment with production data needs a backup and migration plan before the database URL is changed.
$ docker compose stop open-webui [+] Stopping 1/1 ✔ Container open-webui-open-webui-1 Stopped
Open WebUI does not copy existing SQLite data into PostgreSQL just because DATABASE_URL changes. Back up the existing data volume and plan the migration separately when accounts, chats, uploads, or settings already matter.
Related: How to back up and restore Open WebUI data
COMPOSE_PROJECT_NAME=open-webui OPEN_WEBUI_PORT=3000 POSTGRES_PASSWORD=replace-with-a-long-database-password WEBUI_SECRET_KEY=replace-with-the-existing-or-new-webui-secret
Use the existing WEBUI_SECRET_KEY for a deployment that already has users. Changing it can invalidate sessions and break state that depends on the signing key.
Related: How to set WEBUI_SECRET_KEY in Open WebUI
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openwebui
POSTGRES_USER: openwebui
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openwebui -d openwebui"]
interval: 10s
timeout: 5s
retries: 10
volumes:
- postgres-data:/var/lib/postgresql/data
open-webui:
image: ghcr.io/open-webui/open-webui:main
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "${OPEN_WEBUI_PORT:-3000}:8080"
environment:
WEBUI_SECRET_KEY: "${WEBUI_SECRET_KEY:?set WEBUI_SECRET_KEY in .env}"
DATABASE_URL: "postgresql://openwebui:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/openwebui"
volumes:
- open-webui:/app/backend/data
volumes:
postgres-data:
open-webui:
For a managed PostgreSQL service, omit the local postgres service and replace the host, user, password, port, and database name in DATABASE_URL with the provider values. URL-encode special characters in the username or password.
Tool: Docker Compose Healthchecks Checker
$ docker compose config --services postgres open-webui
$ docker compose up -d postgres [+] Running 2/2 ✔ Network open-webui_default Created ✔ Container open-webui-postgres-1 Started
$ docker compose up -d open-webui [+] Running 2/2 ✔ Container open-webui-postgres-1 Healthy ✔ Container open-webui-open-webui-1 Started
Start one Open WebUI instance first when several replicas share the same database. Let startup migrations finish before scaling out the remaining instances.
$ curl -fsS http://127.0.0.1:3000/health
{"status":true}
$ docker compose logs open-webui open-webui-1 | INFO [alembic.runtime.migration] Context impl PostgresqlImpl. open-webui-1 | INFO [alembic.runtime.migration] Will assume transactional DDL. open-webui-1 | INFO [alembic.runtime.migration] Running upgrade -> 7e5b5dc7342b open-webui-1 | INFO [alembic.runtime.migration] Running upgrade 7e5b5dc7342b -> ca81bd47c050, Add config table. ##### snipped #####
The PostgresqlImpl line confirms that migrations are running against PostgreSQL instead of the default local database.
$ docker compose exec postgres psql -U openwebui -d openwebui -c '\dt'
List of relations
Schema | Name | Type | Owner
--------+-------------------------+-------+-----------
public | access_grant | table | openwebui
public | alembic_version | table | openwebui
public | api_key | table | openwebui
public | auth | table | openwebui
public | chat | table | openwebui
public | chat_message | table | openwebui
public | config | table | openwebui
public | user | table | openwebui
##### snipped #####
(43 rows)
Tables such as alembic_version, chat, config, and user show that Open WebUI initialized its schema in the PostgreSQL database.