I updated an Open WebUI Docker install from v0.11.3 to v0.11.4 on my M1 MacBook Air, then deleted the original container and volume and rebuilt the instance from the backup archive alone. The chat, the user, the uploaded file, and even the login session from before the update survived both the update and the restore, and v0.11.4 ran no database migrations on the v0.11.3 data. What made that work is keeping three things while the container itself is replaced: the existing data volume mounted at /app/backend/data, the exact WEBUI_SECRET_KEY, and a backup you have proven you can read. When I changed only the secret key, the old session was rejected with HTTP 401 while every record stayed intact, so a wrong key logs people out rather than deleting data.

The order is: record the current image and mount, stop the container, archive its data, pull a deliberate tag, recreate the container on the same data path, then check chats, settings, uploads, and login before sending traffic to it. The Open WebUI update guide describes the replace-and-recreate sequence; the checks around it are what make an Open WebUI Docker update reversible.

As Docker's volume documentation explains, removing a container does not delete a named volume, but that only protects you when the mount is correct. If an upgrade starts a database migration and fails, preserve the volume and restore a verified backup onto a separate recovery volume before you attempt an image downgrade. Whatever command style you use, verify the existing mount before replacing the container.

Four conditions to confirm before you touch the instance:

  • The data mount must end at /app/backend/data, with the old volume or host directory as its source.
  • The secret key must be the existing value, not a newly generated replacement.
  • A backup is useful only after its archive can be listed, extracted, and checked.
  • A pinned release tag records the image target; :main needs more review.

What happened when I ran this update

On October 5, 2026, I ran the procedure on this page end to end: a fresh v0.11.3 install with one user, one chat answered by a local Ollama model, and one uploaded text file; then a cold backup, an update to v0.11.4 on the same volume, and a restore from the archive into a new volume after the original was deleted. Every check went through the Open WebUI API, and the uploaded file was compared by its sha256 hash.

StepTimeSize or detailData afterwards
Pull v0.11.31,451.69 s1,656.2 MB download, 6.49 GB on diskNot created yet
First start on an empty volume329.86 s to /healthFull migration chain plus model downloadsSeeded: 1 user, 1 chat, 1 file
docker stop11.78 sNo changeVolume untouched
Cold backup with tar czf94.87 s1,009.9 MB archive, 211 entrieswebui.db and uploads/ in the archive
Extract and check the archive6.82 sPRAGMA quick_check returned ok1 chat, 1 user, 1 file in the copy
docker rm0.87 sContainer onlyNamed volume kept
Pull v0.11.41,122.18 s1,492.1 MB download, 5.85 GB on diskNot touched
Start v0.11.4 on the same volume23.81 s to /health0 migrations in the startup logYes: chat, user, file hash, old session (HTTP 200)
Restore the archive into a fresh volume37.17 sOriginal container and volume already deletedRestored from the archive only
Start v0.11.4 on the restored volume56.15 s to /healthSame image, second portYes: same records, old session still HTTP 200
Restart with a different WEBUI_SECRET_KEYNot timedSame restored volumeData and password sign-in intact; old session HTTP 401

The archive was almost all cache. Of the 1,066.7 MiB volume, 1,065.4 MiB was cache/: 922.7 MiB of embedding models (all-MiniLM-L6-v2 and bge-micro-v2) and 141.1 MiB of faster-whisper-base, all downloaded by Open WebUI on first start. The user data was tiny: webui.db at 668 KiB, uploads/ at 4 KiB, and vector_db/ at 356 KiB. A 1 GB archive for one chat is therefore normal, not a sign the backup grabbed the wrong directory. I kept cache/ in the archive, and I suspect that is part of why the updated container was healthy in 23.81 seconds against 329.86 for the first start, although the first start was also building the database from nothing.

The download, not the restart, was the outage. v0.11.4 shared 1 of its 15 layers with v0.11.3, so the update re-downloaded almost the whole image, which took 1,122.18 seconds on my connection (roughly 1.1-1.6 MB/s). Because I pulled after removing the container, the instance was down for all of it. I had to: each full image took more than 5 GB in Docker Desktop's image store, so I deleted the v0.11.3 image to make room, which also meant a rollback to the old tag would have started with another long pull. If your disk holds both images, pull the new tag before you stop anything.

Ollama on loopback was still reachable. Native Ollama listened only on 127.0.0.1:11434 with OLLAMA_HOST unset, yet a curl to /api/version on host.docker.internal:11434 from inside the Open WebUI container returned 0.35.1 and the model list showed the Qwen model. That is Docker Desktop for Mac behaviour; I did not test a Linux host.

The safe Open WebUI Docker update order

Open WebUI's documented Docker update is a container replacement: remove the old container, pull the selected image, and recreate it with /app/backend/data mounted. The safe version adds proof and recovery checks:

  1. Record the image, mounts, environment source, port, and current tag.
  2. Confirm the data source is the expected named volume or bind directory.
  3. Preserve WEBUI_SECRET_KEY, stop the container, and create a cold backup.
  4. List and extract the archive, then check the SQLite copy when webui.db is present.
  5. Pull the deliberate tag, recreate only Open WebUI, and verify the mount, logs, login, chats, settings, and uploads.

Open WebUI documents :main, :dev, version tags, and variants such as vX.Y.Z-cuda and vX.Y.Z-ollama. On September 24, 2026, the releases page listed v0.11.4 as latest, after v0.11.3. Releases move quickly, so review the current release notes, then set WEBUI_TAG to the version you intend to deploy.

Open WebUI's update documentation states: "Your data (chats, users, settings, uploads) lives in a Docker volume or local database, not inside the container."

Open WebUI Docker persistent volume: what survives replacement

A Docker container has a writable layer that belongs to it. A named volume survives container removal, while a bind mount maps a host directory into the container. The Open WebUI FAQ gives the persistent path as /app/backend/data, and its source must be the existing instance storage.

Verify the mount while the old container still exists. This inspection is read-only and runs in a Linux or macOS shell; replace the example container name if yours differs:

CONTAINER="open-webui" # example container name
docker inspect "$CONTAINER" --format '{{range .Mounts}}{{println .Type .Name .Source "->" .Destination "rw=" .RW}}{{end}}'

The output row with destination /app/backend/data identifies the actual source. In my drill it printed volume aw-owui-data /var/lib/docker/volumes/aw-owui-data/_data -> /app/backend/data rw= true, and the same row again after the update. For a named volume, copy the Name value printed above and inspect that volume:

SOURCE_VOLUME="actual-volume-name"
docker volume inspect "$SOURCE_VOLUME"

For a bind mount, use the Source value printed above instead of docker volume inspect:

SOURCE_PATH="/actual/host/path"
ls -la "$SOURCE_PATH"

open-webui and actual-volume-name are examples only. A Compose project may prefix a volume key, such as project_open-webui, and a bind deployment has no Docker volume to inspect. Docker's example Linux mountpoint is under /var/lib/docker/volumes/.../_data, but Docker Desktop may keep data inside its VM, so do not guess a host path. On my Mac the source column showed that Linux-style path, so I read the volume through a helper container, as in the backup step below, rather than looking for it from the macOS shell.

If the old container has no data mount, stop the update. A replacement will start with an empty writable layer, even if old data remains inside the old container. Correct any new-volume, spelling, or slash error first. Operator reports on Reddit describe this failure pattern; it is not a specific release defect.

Open WebUI Docker version pinning: which strategy fits?

The tag determines how deliberately you accept change. An image tag alone cannot reverse a database migration.

StrategyGood fitMain riskRecovery posture
:mainAn operator who reviews every updateA later pull can resolve to a different build; migration may need a fresh backupRecord the image, keep the previous tag, and back up each time
Pinned release tag such as vX.Y.ZProduction needing a reproducible targetA release can still migrate the schema or contain a bugPin current and previous tags; restore data if migration changed it
Staging instanceTesting a tag on a separate data copyA second container, port, and data set add upkeepUse a separate volume and port; never share production storage
Automated update toolsA defined backup, alerting, and rollback policyWatchtower updates automatically; alerts do not restore dataPrefer notification or review until recovery is routine

The first row is not hypothetical. On October 5, 2026, the arm64 manifest for :main already had different layers from :v0.11.4, which was the latest release that day, so a :main pull gave a build that differed from the newest release. The registry also lists slim variants; v0.11.4-slim was 175.9 MB compressed for arm64 against 1,492.1 MB for the full image, though I did not run it.

Open WebUI's update guide names Diun for notifications, Watchtower for automatic updates, and What's Up Docker for reviewed updates. To pin, replace a floating tag such as main with an explicit release tag in the Docker Run command or Compose file, and record that tag with the backup and rollback notes. Pinning makes an update reproducible and stops an ordinary container recreation from silently pulling a newer application version. Staging is the safest place to watch a migration.

Pre-update checklist

Complete these checks while the current instance is available:

  • Current image: save the current docker inspect output and the exact image reference.
  • Data mount: confirm its source and /app/backend/data destination.
  • Secret key: retrieve the existing WEBUI_SECRET_KEY from its secret store.
  • Selected tag: record the next tag and the previous recovery tag.
  • Free disk space: leave room for the archive and a whole second image. In my run the images took 6.49 GB and 5.85 GB in Docker's store, the archive was 1,009.9 MB, and only one layer was shared between the two versions.
  • Readable archive: list, extract, and inspect webui.db when present.
  • Recovery path: choose a fresh volume and port for migration failure.

Run the checks in that order: mount, key, archive, tag, then replacement. That catches the expensive mistake before a removal command.

Do not rotate the secret key during an image update. Open WebUI uses it for JWT signing and OAuth-session encryption, and rotation invalidates existing sessions. Keep the existing value with the database and uploaded files as part of the recovery path. When I restarted the restored instance with a different key, the session token issued before the update came back HTTP 401, while password sign-in, the chat, and the uploaded file were all still there. A changed key is a logout, not data loss, at least for a password account; I did not test OAuth logins.

Update Open WebUI Docker with Run or Compose

The two examples have different scopes. Whichever you use, preserve the existing ports, environment variables, GPU flags, image, secret, and data mount.

Docker Run deployment

Stop the instance and create the archive in the next section. If the disk has room for both images, run the pull below before stopping, because the download is the slow part: 1,122.18 seconds in my run against 23.81 seconds for the restart. After recording the current image, set WEBUI_TAG to the release tag you reviewed on the official releases page. In a Linux or macOS shell, replace vX.Y.Z here, and the env-file path in the next step, deliberately:

WEBUI_TAG="vX.Y.Z"
docker pull "ghcr.io/open-webui/open-webui:${WEBUI_TAG}"
Warning: The next command removes the container. It does not remove a named volume, but it removes the container's writable layer. Run it only after mount inspection and backup verification. Never replace it with docker volume rm <production-volume>.

Recreate the container with the same source shown by docker inspect and the existing secret file:

CONTAINER="open-webui" # replace with the current container name
DATA_VOLUME="actual-volume-name" # replace with the inspected volume name
WEBUI_TAG="vX.Y.Z" # use the same reviewed tag as above
docker rm "$CONTAINER"
docker run -d --name "$CONTAINER" -p 3000:8080 --add-host=host.docker.internal:host-gateway -v "${DATA_VOLUME}:/app/backend/data" --env-file ./open-webui.env "ghcr.io/open-webui/open-webui:${WEBUI_TAG}"

My env file held two lines, WEBUI_SECRET_KEY and an OLLAMA_BASE_URL pointing at host.docker.internal:11434, and the --add-host flag maps that name to the Docker host. Docker Desktop provides that name on its own; on Linux the flag is what creates it, and I did not test a Linux host. If the old container had other flags, carry them over.

Then wait for the health endpoint, confirm the mount, and read the startup log for migrations:

until curl -fs http://localhost:3000/health >/dev/null; do sleep 5; done
docker inspect "$CONTAINER" --format '{{range .Mounts}}{{println .Type .Name .Source "->" .Destination "rw=" .RW}}{{end}}'
docker logs "$CONTAINER" 2>&1 | grep -iE 'alembic|migrat|upgrade'

On v0.11.3 to v0.11.4 the log showed only the two alembic setup lines and no Running upgrade line, so the schema did not change. A first start on an empty volume prints a long chain of Running upgrade lines and downloads models into cache/; mine took 329.86 seconds to answer /health, so give a new or heavily migrated instance several minutes before deciding it is stuck.

The values above are examples. DATA_VOLUME must be the actual named volume identified from the old container; open-webui is not universal. For a bind mount, replace the -v value with "${DATA_PATH}:/app/backend/data" and set DATA_PATH to the exact old host path. Keep the existing port, environment variables, GPU flags, and secret settings from the old container.

Docker Compose deployment

Set WEBUI_TAG in the shell or Compose .env file to the release tag you reviewed, then keep the existing Compose volume key and secret reference stable. The volume key below is an example only:

services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:${WEBUI_TAG}
    ports:
      - "3000:8080"
    volumes:
      - open-webui:/app/backend/data # example key; keep your existing mapping
    environment:
      WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY}

volumes:
  open-webui: # example only; Compose may create project_open-webui

Review the resolved configuration, then update only the service unless a provider needs a restart:

docker compose config
docker compose pull open-webui
docker compose up -d --no-deps open-webui
docker compose ps open-webui

Do not run docker compose down -v as an update shortcut: the -v option removes declared named volumes. After either style, inspect the mount again and check startup logs for migration errors.

Open WebUI Docker backup and restore: verify first

A useful backup covers webui.db, uploads/, and vector_db/ in the data directory. Stop the container, identify the exact source from docker inspect, and then write a cold archive. For a named volume, run this while the container is stopped and replace the source name with the one you inspected:

SOURCE_VOLUME="actual-volume-name"
BACKUP_FILE="openwebui-$(date +%Y%m%d).tar.gz"
docker run --rm \
  --mount "type=volume,src=${SOURCE_VOLUME},dst=/data,readonly" \
  --mount "type=bind,src=${PWD},dst=/backup" \
  alpine tar czf "/backup/${BACKUP_FILE}" -C /data .

For a bind mount, archive the exact Source directory recorded by docker inspect instead, and run the command from outside the data directory:

SOURCE_PATH="/actual/host/path"
BACKUP_FILE="$PWD/openwebui-$(date +%Y%m%d).tar.gz"
tar czf "$BACKUP_FILE" -C "$SOURCE_PATH" .

Do not substitute a convenient directory. The community backup tutorial on the Open WebUI docs site treats uploads/ as part of the instance, so a backup without it is incomplete even when SQLite is present. The volume name open-webui is only an example; the SOURCE_VOLUME value must match the running container's actual source.

Check that the archive is readable before updating:

ARCHIVE="openwebui-YYYYMMDD.tar.gz"
tar -tzf "$ARCHIVE" | sed -n '1,40p'
tar -tzf "$ARCHIVE" | grep -E '(^|/)webui.db$|(^|/)uploads/'
CHECK_DIR="$(mktemp -d)"
tar -xzf "$ARCHIVE" -C "$CHECK_DIR"
sqlite3 "$CHECK_DIR/webui.db" 'PRAGMA quick_check;'
sqlite3 "$CHECK_DIR/webui.db" 'select version_num from alembic_version;'

The first command checks archive structure. The second looks for the database and uploads. Successful extraction proves the archive unpacks, and PRAGMA quick_check should return ok for a readable SQLite copy. The archive is an instance copy, not only a database dump, which matters when users depend on uploaded files or vector data. The last line records the schema version the backup was taken at; mine read d4c1a8e37b62. Write it next to the archive name, because if the new image's log later shows Running upgrade lines, you know this archive predates the change. On my archive, the listing had 211 entries, extraction on the Mac took 6.82 seconds, and quick_check returned ok. A fresh-volume restore is the stronger test; mine is in the next section.

Open WebUI Docker rollback after a failure

An image rollback and a data restore solve different failures. If the new container fails before changing the schema, the previous image with the same volume may be enough. After a migration, an older image may not understand the changed database. Restore the pre-update archive instead of repeatedly restarting the failed container.

Use a fresh recovery volume first. It keeps the current volume intact while you check the old image and secret.

Warning: The restore command writes files into the recovery volume. Confirm that the volume is empty or disposable, stop every container using it, and never set RECOVERY_VOLUME to the production volume. Extraction can overwrite current files.
RECOVERY_VOLUME="open-webui-recovery"
PREVIOUS_TAG="vX.Y.Z" # set to the previous reviewed release
docker volume create "$RECOVERY_VOLUME"
docker run --rm \
  --mount "type=volume,src=${RECOVERY_VOLUME},dst=/data" \
  --mount "type=bind,src=${PWD},dst=/backup,readonly" \
  alpine tar xzf /backup/openwebui-YYYYMMDD.tar.gz -C /data
docker run -d --name "$RECOVERY_VOLUME" -p 3001:8080 --add-host=host.docker.internal:host-gateway -v "${RECOVERY_VOLUME}:/app/backend/data" --env-file ./open-webui.env "ghcr.io/open-webui/open-webui:${PREVIOUS_TAG}"

After it starts, check login, a known chat, a setting, and an uploaded file. If those checks pass, plan the cutover.

When I ran this restore after deleting the original container and volume, extraction into the fresh volume took 37.17 seconds, the recovered instance answered /health in 56.15 seconds on the second port, and the chat, the user, the uploaded file, and the pre-update login session were all there. I restored onto v0.11.4, not the previous tag: no migration failed, and I had removed the v0.11.3 image for disk space. The downgrade half of this section is the part I have not tried myself. If recovery also fails, keep both volumes and follow Open WebUI's migration guidance rather than improvising Alembic commands against production.

The release notes say failed upgrades stop at the migration error instead of starting half-updated, including failures after 0.11.0 through 0.11.2. An older image is still not a guaranteed downgrade target, so treat the verified backup as the recovery boundary.

Open WebUI Docker symptoms and fixes

If an update appears to remove data or chats, compare the old and new mount, image, and secret first.

SymptomCheck firstFix or decision
Chats are missingDoes the new container mount the old source at /app/backend/data? Is webui.db there?Stop creating new volumes. Reattach the source or restore to a fresh recovery volume.
Uploaded files are missingDoes the archive contain uploads/ and does the new mount expose it?Reuse the complete data mount. Do not rely on a database-only backup.
Settings changedIs the replacement reading the old webui.db rather than a new writable layer?Correct the mount before changing application settings or restoring data.
Users were logged outIs WEBUI_SECRET_KEY exactly the old value?Restore the original key. Changing it invalidates existing sessions; in my test the old session got HTTP 401 while data and password sign-in survived.
Error decrypting tokens appearsWas the key omitted, renamed, or regenerated?Put the original key back and restart. Treat a lost key as credential recovery, not an image-tag problem.
Startup reports a migration failureWhat does docker logs open-webui show, and was the database copied?Preserve the failed volume. Test the previous tag with a restored copy, or use the official procedure.
Model-provider connections are missingIs the UI data intact, with only an Ollama endpoint failing to resolve or connect?Check OLLAMA_BASE_URL and the container network before changing the data volume. On Docker Desktop for Mac, host.docker.internal:11434 reached an Ollama bound to loopback.

Keep the last row narrow. Ollama belongs here only for an endpoint or network problem; a provider outage does not explain missing chats, users, or uploads.

If maintaining a Docker web UI is more than a single user needs, the best Ollama GUI guide compares desktop and browser front-ends that run without a container.

How I tested this

  • Machine: a MacBook Air with an Apple M1 and 8 GB of unified memory, running macOS with desktop apps open and heavy swap in use.
  • Date: October 5, 2026.
  • Versions: Docker Desktop with engine 28.3.2 and the containerd image store; native macOS Ollama 0.35.1 on the host, left on its default loopback bind with OLLAMA_HOST unset, serving a small Qwen2.5 model.
  • Images: ghcr.io/open-webui/open-webui:v0.11.3, then ghcr.io/open-webui/open-webui:v0.11.4, the latest release on the run date. Backup and restore used alpine:3 helper containers.
  • What was run: a scripted drill, one phase at a time. I installed v0.11.3 on a named volume with an env file holding WEBUI_SECRET_KEY and OLLAMA_BASE_URL, created an admin user with dummy credentials, sent one chat completion through Open WebUI to Ollama, saved it as a chat, and uploaded one small text file. I stopped the container, made a cold archive with a helper container, listed and extracted it, and ran PRAGMA quick_check on the copy. I removed the container and the old image, pulled v0.11.4, recreated the container on the same volume and env file, and checked the version, the mount, the startup log, the old session token, password sign-in, the chat, the users, and the uploaded file's sha256. Then I deleted the original container and volume, restored the archive into a fresh volume, started v0.11.4 on it, checked the same records, and restarted it once with a different secret key.
  • Not tested: rolling back to the previous tag after a failed migration, since no migration failed and the v0.11.3 image had been removed; Docker Compose and bind mounts; Watchtower, Diun, and What's Up Docker; CUDA and other GPU image variants; a Linux Docker host, where the host.docker.internal result may differ; and multi-user instances with real traffic, OAuth logins, or a large chat history. Pull times reflect my connection, not the images.
  • Raw record: every metric is in the public results file, next to the capture scripts, the per-phase log, the archive listing, and the v0.11.4 startup log lines.

References