diff --git a/docker/README.md b/docker/README.md
index d64d5789..e7a5611e 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,5 +1,8 @@
# Vaultwarden Container Building
+For optional updates from the admin diagnostics page, see
+[Admin Docker updates](updater/README.md).
+
To build and release new testing and stable releases of Vaultwarden we use `docker buildx bake`.
This can be used locally by running the command yourself, but it is also used by GitHub Actions.
diff --git a/docker/updater/README.md b/docker/updater/README.md
new file mode 100644
index 00000000..38d53578
--- /dev/null
+++ b/docker/updater/README.md
@@ -0,0 +1,141 @@
+# Admin Docker updates
+
+Diagnostics reuses the yellow **Update** badge beside **Server Installed** as a
+button. While the host downloads the configured image, backs up the vault, and
+replaces the container, it shows **Updating…**.
+After the new container is healthy, the page reloads. Refreshing the browser does
+not cancel the update. An unchanged image does not restart the service.
+
+The server operator must first deploy an image containing admin update support.
+Compatible images carry the `org.vaultwarden.admin-updates=1` label. Older images
+without this feature are rejected so an update cannot remove the admin endpoint.
+
+## Supported deployment
+
+- Linux host with Python 3.10+, Docker, and Docker Compose v2+.
+- One existing Compose service, with its dedicated data directory bind-mounted at
+ `/data`, a healthy Docker health check, and SQLite/all persistent data under
+ `/data`. Named data volumes, external databases/storage, symlinks in data, and
+ additional writable mounts are deliberately rejected before the service stops.
+- Custom environment files and file-based storage configuration are not supported.
+ `DATA_FOLDER` must be `/data` and `CONFIG_FILE` must use `/data/config.json`.
+- The existing Compose files remain the source of truth for ports, environment,
+ mounts, networks, and restart policy. Do not modify them or redeploy the same
+ service while an update is active.
+ Unapplied Compose configuration changes are rejected before an update starts.
+- Admin authentication is required. `DISABLE_ADMIN_TOKEN=true` disables updates.
+
+The updater runs on the Docker host and survives Vaultwarden container replacement.
+Vaultwarden receives only the updater's Unix socket; it never receives the Docker
+socket. The browser cannot choose images, commands, or target services. Protect the
+updater configuration and socket as administrative interfaces.
+
+## One-time installation
+
+Adapt every example path, project name, and service name to the **existing** stack.
+Do not replace the existing Compose file with a new stack. The example registry
+name is a placeholder; replace it with the repository you publish or trust.
+
+1. Build and publish a compatible image to your registry. For an amd64 server, for example:
+
+ ```sh
+ docker buildx build --platform linux/amd64 -f docker/Dockerfile.debian \
+ -t registry.example.com/vaultwarden:latest --push .
+ ```
+
+ Use the server's actual architecture. Publish a versioned tag as well if you need
+ release history. Only move `latest` to a reviewed, tested image. Image publishing
+ is managed separately; the updater only deploys the configured image channel.
+
+2. Install `updater.py` at `/opt/vaultwarden-updater/updater.py`, copy
+ `config.example.json` to `/etc/vaultwarden-updater.json`, and fill in the actual
+ Compose files (in their existing order), project, service, data path, and image.
+ Use absolute paths. Keep `/var/lib/vaultwarden-updater` outside the data directory.
+ Keep the config and script root-owned and not writable by the application user.
+ Log in to a private registry on the host as the updater's OS user, if necessary.
+
+3. Install `vaultwarden-updater.service` in `/etc/systemd/system/`, then run:
+
+ ```sh
+ sudo systemctl daemon-reload
+ sudo systemctl enable --now vaultwarden-updater
+ ```
+
+4. Add only these settings to the existing Vaultwarden service, alongside its
+ existing configuration, and deploy the first compatible image through your existing
+ deployment procedure:
+
+ ```yaml
+ environment:
+ UPDATER_SOCKET: /run/vaultwarden-updater/updater.sock
+ volumes:
+ - /run/vaultwarden-updater:/run/vaultwarden-updater:ro
+ ```
+
+ Mount the **directory**, so restarting the updater can replace its socket.
+ The service defaults to root:root socket permissions `0660`, matching the
+ default root Vaultwarden container. For a non-root container, set the unit's
+ `Group` and the container's supplemental numeric group to the same dedicated
+ group. The directory requires group traversal permission.
+
+5. Open **Admin → Diagnostics → Update**. Do the first update on a test deployment
+ and verify vault login, sync, and attachments before enabling production use.
+
+## Deployment and recovery
+
+The updater downloads the configured tag while the old service remains available.
+It then pins the downloaded **image ID**, stops just the selected service, archives
+all of `/data`, and recreates that service with `--no-deps --no-build --pull never`.
+Backups are stored at `state_directory/backups//data.tar`, along with
+the previous and target image IDs in `deployment.json`. Protect these archives as
+vault data and manage their retention/free disk space on the host.
+
+The selected image is persisted in `state_directory/image.override.json`. Include
+this file **last** in future manual Compose commands; otherwise a manual deployment
+could restore the old image from the base Compose file. For example:
+
+```sh
+docker compose --project-directory /opt/vaultwarden --project-name vaultwarden \
+ -f /opt/vaultwarden/compose.yml \
+ -f /var/lib/vaultwarden-updater/image.override.json ps
+```
+
+Failures before stopping the service leave it running. Failures after a stop was
+requested, and updater interruptions, require host inspection and block subsequent
+updates. `status.json` records the last completed stage. Full Docker output and
+container environment values are never sent to the browser.
+
+Do not simply run an old image against a database migrated by a new image. To
+recover, stop the updater, inspect the container and the saved deployment metadata,
+and repair the deployment. If restoring the previous version is necessary, stop
+the vault, preserve its current data separately, restore the selected `data.tar`
+backup with its ownership and permissions, set `image.override.json` to the saved
+previous image ID, and start the service. Restoration may discard writes accepted
+after the backup, so it is an operator decision, never an automatic action.
+
+After checking the running service, clear the recovery lock while the updater
+service is stopped, then start the updater again:
+
+```sh
+sudo systemctl stop vaultwarden-updater
+sudo python3 /opt/vaultwarden-updater/updater.py /etc/vaultwarden-updater.json \
+ --acknowledge-recovery
+sudo systemctl start vaultwarden-updater
+```
+
+This command verifies the container's health and data configuration before clearing
+the lock. Keep previous images until the new release is stable; image pruning can
+otherwise remove the saved rollback target.
+
+## Local verification
+
+```sh
+python3 -m unittest discover -s docker/updater -v
+cargo check --locked --features sqlite
+node --check src/static/scripts/admin_updates.js
+```
+
+The Python flow tests use a controlled Docker adapter and real temporary backup
+archives. They cover unchanged images, rejected images/storage, download/recreation/
+health failures, duplicate requests, and interrupted updater recovery. They do not
+replace a test of an actual Docker deployment.