Estimated reading time at 200 wpm: 7 minutes
Do I care if you or 99% of people don’t know anything about this, or couldn’t care less? I do not! Open WebUI offered me version 0.10.2 this morning. I was on 0.9.6. A small number, a small hop, or so it looks. It isn’t. I have been burned before. An earlier upgrade almost wiped my account and orphaned the volume holding everything. I recovered it, but only because the data still existed somewhere I could reach. That experience left a residue. So this time I stopped, and thought, before clicking anything.
Whether or not you agree our Fat Disclaimer applies
What follows is the reasoning, the sequence, and the two detours. The detours are the interesting part.
Why 0.9 to 0.10 is not a small hop
Open WebUI stores its state in a database. Chats, settings, API keys, connectors, the lot. When the developers change the shape of that database — new tables, new columns, renamed keys — they ship migrations: scripts that rewrite the existing database into the new shape on first startup.
The tool doing this is Alembic. It tracks which migrations have been applied and runs any that haven’t. Migrations are one-way.
That is the whole problem in a sentence. Once Alembic has reshaped your database for 0.10, an older version of the application no longer recognises it. You cannot simply put the old container back and carry on. The upstream release notes say this outright: downgrading after the 0.10 migrations is not supported.
So the version number is misleading. 0.9.6 to 0.10.2 looks incremental. Structurally it is a one-way door.
If you are going through a one-way door, you want a photograph of the room you left.
The photograph: backing up the volume
Docker separates the container from the volume. The container is the running application — disposable, replaceable, rebuilt from an image. The volume is the data. Deleting a container does not touch its volume. This is by design, and it is the only reason upgrades are survivable at all.
My volume was called open-webui, mounted at /app/backend/data.
To back it up, you cannot just copy a folder in Windows Explorer. On Docker Desktop the volume lives inside a Linux virtual machine, not on your visible filesystem. You have to reach in from another container:
docker stop open-webui
docker run --rm -v open-webui:/data -v "C:\path\to\backups:/backup" alpine tar czf /backup/owui-2026-07-11.tar.gz -C /data .
Read that middle line carefully. It starts a throwaway Alpine Linux container, mounts my data volume into it as /data, mounts a real Windows folder into it as /backup, and tars one into the other. The --rm means the helper container evaporates afterwards. It exists for about ninety seconds and does one job.
Result: 1.1 GB. A real backup, not an empty shell.
Detour one. My first attempt failed with tar: can't open ... Permission denied. The reason was banal. I was sitting in C:\WINDOWS\system32, and Docker cannot write there. Nothing was broken. I had simply run the command from a directory Windows protects. Moved to a proper backup folder, re-ran, done.
Worth naming, because a permission error at that moment feels like catastrophe. It wasn’t. It was a working directory.
Reconstructing the container
Here is the part people tend to get wrong.
To upgrade, you must destroy the old container and create a new one from the new image. Which means you need to know exactly how the old container was created. Get a flag wrong and you produce a container that runs perfectly and is connected to nothing.
Some people use Docker Compose, where the recipe lives in a file and this problem doesn’t arise. I didn’t. I had used a bare docker run at some point in the past and had no record of it.
So I interrogated the container itself:
docker inspect open-webui --format "{{json .Config.Env}}"
docker inspect open-webui --format "{{json .HostConfig.PortBindings}} {{json .Mounts}}"
docker inspect open-webui --format "{{.Config.Image}}"
docker inspect reports a container’s full configuration. Ports, environment variables, mounts, restart policy, source image. Everything needed to rebuild it faithfully.
Two things emerged, both reassuring.
First, every environment variable was an image default. Nothing bespoke. My API keys were not baked into the container — they live inside the data volume, which means they were already in the tarball.
Second, WEBUI_SECRET_KEY was empty. That variable signs session tokens. Empty means it is stored in a file inside the volume instead. So the volume carried my login too. Nothing would be invalidated.
The reconstructed command:
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
Keeping a way back
Before pulling anything, one more precaution.
The image was tagged :main. Tags are labels, not identities. Pull :main again and the label moves to the new image. The old one is still on disk, but nameless — an orphan you would have to hunt by hash.
So I gave it a name of its own:
docker tag ghcr.io/open-webui/open-webui:main owui-rollback:0.9.6
Now, if the migration had failed catastrophically, I could restore the tarball into a fresh volume and run the old image against it. A real rollback path, not a hopeful one.
This is the step most upgrade guides omit, and it costs nothing.
The upgrade
docker pull ghcr.io/open-webui/open-webui:main
docker stop open-webui
docker rm open-webui
docker run -d ... # the reconstructed command
docker logs -f open-webui
docker rm removes the container. It does not touch the volume. That distinction is the entire basis of the procedure.
Detour two: the hang that wasn’t
The docker run appeared to hang. No container ID came back. The prompt sat there.
I opened a second terminal and checked:
docker ps -a --filter name=open-webui
Empty. No container at all. Meanwhile docker volume ls still showed open-webui, so the data was untouched — which was the only thing that actually mattered, and worth establishing before doing anything else.
The cause was mundane. My initial hypothesis was PowerShell’s QuickEdit mode, where clicking inside the console pauses the running process until you press Escape. Pressing Escape revealed the truth: the command had never been submitted. The Enter keystroke had gone astray. The prompt was simply waiting.
Nothing had failed. Nothing had hung. A key had not landed.
I record this deliberately. In the middle of a procedure you are frightened of, ambiguity reads as disaster. The discipline is to determine what state you are actually in before acting on the state you fear you are in. Check whether the container exists. Check whether the volume exists. Then decide.
Panic at that moment — killing Docker, restarting the daemon, re-running things blindly — is exactly how a non-event becomes a real one.
What the logs showed
Four Alembic migrations, in sequence:
- reshape config to per-key rows
- add context summary to chat message
- add memory type
- add memory path and meta
Then the secret key loaded from file rather than regenerated. Then 63 config keys renamed from rag.web.* to web.*. Then the server started.
v0.10.2 — building the best AI user interface.
The warnings were noise. A CORS default that has always been there. A deprecated authlib.jose import. An UNEXPECTED key in the embedding model load report, which is normal for that model. None of them mean anything.
Verification
The upgrade is not finished when the container starts. It is finished when you have checked:
- Version reports 0.10.2, latest.
- Still logged in — so the secret key survived.
- Chats present.
- Model provider answering.
- Connectors intact.
All held. The backup stays until every box is ticked, and only then becomes an ordinary old file rather than a lifeline.
The general lesson
Nothing here was clever. The whole procedure reduces to four moves:
- Establish what you would lose, and take a copy of it.
- Establish exactly how the thing was built, before you unbuild it.
- Preserve a route back that you have actually thought through.
- Distinguish, at every moment, between what has gone wrong and what merely looks as though it has.
The two problems I hit were a wrong working directory and a keystroke that never landed. Neither was technical. Both would have felt like disasters had I not already known my data was safe.
That is what the backup buys. Not merely recovery. Composure.
Software fails loudly. Procedure fails quietly. The second is the one to guard against.










