WebUI for managing Docker container updates with automated data backups, snapshots and rollbacks.
549
A web interface for maintaining containers on a single Docker host. From the browser you can check for image updates, back up a container's data, create restore points, roll back to any of them, switch a container to a different tag, and schedule automatic update checks per container.
The interface runs as a small Alpine container (kleinriese/docker-update-ui,
available for linux/amd64 and linux/arm64) and talks to the host through the
Docker socket. It is fully bilingual: German and English.
N/A in the directory
column.tar.gz) taken while the container is stopped, so the archive
is consistent.<repository>:rollback-<timestamp> before anything changes.rollback_<container>_<timestamp>.sh) and a JSON snapshot
of the container configuration next to every restore point.1.2.3), a full reference in the same repository
or a digest, and the container is recreated on that image – useful to move
away from latest or back from a rollback tag.max_rollbacks restore points per container are
kept; older backups, scripts, snapshots and rollback images are deleted.
Rollback images without a restore point can be removed from the interface./config.cp .env.example .env
# open .env and set WEB_PASSWORD
docker compose up -d --build
Open http://<host>:8080 and sign in with the password from WEB_PASSWORD.
Without Compose (prebuilt image):
docker run -d --name docker-update-ui \
--restart unless-stopped \
-p 8080:8080 \
-e WEB_PASSWORD='change-me' \
-e TZ=Europe/Berlin \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /docker/backups:/docker/backups \
-v "$PWD/data:/config" \
-v /docker/homeassistant:/docker/homeassistant \
kleinriese/docker-update-ui:latest
The Compose file builds the image from this repository. To use the published
image instead, replace the image: line in docker-compose.yml with
kleinriese/docker-update-ui:latest and drop the build: line, or use the
docker run command shown above.
The footer shows the version and the commit the running image was built from, so a deployment can be compared with the repository. Pass the commit when building, otherwise the footer says »commit unknown«:
GIT_COMMIT=$(git rev-parse --short HEAD) docker compose build
docker compose up -d --force-recreate
For a plain docker build:
docker build --build-arg GIT_COMMIT="$(git rev-parse --short HEAD)" \
-t kleinriese/docker-update-ui:1.3.0 .
Alternatively put GIT_COMMIT=... into .env; it is passed into the container
and takes precedence over the value baked in at build time.
The application works with the paths it sees inside its own container. Host paths therefore have to be mounted 1:1 – host path = container path:
| Host | Container | Purpose |
|---|---|---|
/var/run/docker.sock | /var/run/docker.sock | Docker API |
/docker/backups | /docker/backups | Backups, scripts, snapshots |
./data | /config | Settings, container options |
/docker/homeassistant | /docker/homeassistant | Example data directory |
Every data directory that should be backed up or restored must be mounted as well. Read access is enough for backups; restores and rollbacks need write access. The interface warns when the backup directory is not on a mounted volume, because backups would then disappear with the container layer.
1.2.3, latest or a full reference. A restore
point is created first; without data backup only the image is saved.Every restore point also contains a standalone script that can be executed on the host, independently of the web interface:
sudo bash /docker/backups/<container>/rollback_<container>_<timestamp>.sh
Each container can be checked automatically:
minute hour day month weekday
(0 = Sunday). Examples: 0 */6 * * * every six hours, 30 4 * * * daily at
04:30, 0 5 * * 1 Mondays at 05:00. Invalid expressions are rejected when
saving, including ones that never match (0 0 30 2 *).Fixed tags are checked as well: a registry may re-publish the same tag, and the
digest comparison catches that. Only an image pinned by digest (repo@sha256:…)
is immutable and therefore skipped.
The check compares the registry manifest digest with the digest of the image the container is running – not with the image the tag currently points to. A new image that was already pulled locally therefore still counts as an update. For multi-architecture images the digest of the matching platform is used; when the running image has no repository digest (for example a locally built image) or the daemon cannot provide a comparable value, the interface falls back to the regular pull-based check. Private registries use the credentials the Docker daemon already has.
The scheduler runs as a thread inside the tool container: it looks every 30
seconds for due entries, skips containers with a running job, the tool's own
container, rollback tags and images pinned by digest. Missed runs are caught up
once after a restart, not collected. Times use the container's time zone (TZ).
Results appear on the container page (last run, method, compared digests, next run) and in the overview table. A manual update replaces the stored result with »up to date«, a rollback or tag switch clears it, so a stale »update available« badge never survives an image change. The »check and notify« action and automatic updates send an email through the SMTP server from the settings:
Subject: Docker update: homeassistant – UPDATE AVAILABLE
Docker update: homeassistant – UPDATE AVAILABLE
(ghcr.io/home-assistant/home-assistant:latest)
The password is read from SMTP_PASSWORD only; it is never stored in
/config nor rendered into the interface. Without SMTP server and recipient
the notification is skipped and a note appears in the container's check log.
Everything can be changed in the Settings page; the environment variables only provide defaults:
| Variable | Default | Meaning |
|---|---|---|
WEB_PASSWORD | – (required) | Password for signing in |
BACKUP_DIR | /docker/backups | Base directory for backups |
BROWSE_DIR | /docker | Start directory for the folder picker |
MAX_ROLLBACKS | 3 | Restore points kept per container |
CONFIG_DIR | /config | Persistent settings |
TZ | Europe/Berlin | Time zone for logs, timestamps and schedules |
SMTP_HOST | – | SMTP server for notifications |
SMTP_PORT | 587 | SMTP port |
SMTP_SECURITY | starttls | none, starttls or ssl |
SMTP_USER | – | SMTP username (also the default sender) |
SMTP_PASSWORD | – | SMTP password, only read from the environment |
SMTP_FROM | – | Sender address |
SMTP_TO | – | Recipient(s), comma-separated |
SMTP_TIMEOUT | 10 | Timeout for the SMTP connection in seconds |
AUTO_CHECK_CRON | 0 4 * * * | Suggested schedule for new entries |
AUTO_CHECK_TICK | 30 | Scheduler interval in seconds (minimum 5) |
SESSION_TTL | 43200 | Session lifetime in seconds |
COOKIE_SECURE | 0 | 1 when the UI is only reachable via HTTPS |
ALLOWED_ORIGINS | – | Additional host names for the origin check (reverse proxy) |
CSRF_PROTECTION | 1 | 0 disables the origin check (only with other protection) |
STOP_TIMEOUT | 30 | Grace period when stopping a container |
DOCKER_TIMEOUT | 600 | HTTP timeout for Docker calls |
GIT_COMMIT | – | Commit shown in the footer (also a build argument) |
SELF_CONTAINER | hostname | Name/ID of the tool container (self-update guard) |
UI_PORT | 8080 | Host port mapped by docker-compose.yml |
Access to /var/run/docker.sock equals root on the host. Therefore:
WEB_PASSWORD and use a strong password,COOKIE_SECURE=1 once HTTPS is in front,cap_drop/read_only for the tool container.The application ships with password login, server-side session cookies
(HttpOnly, SameSite=Strict), a brute-force brake, an origin check for
writing requests and a restrictive content security policy.
The origin check compares the Origin header with the requested host. It accepts
the same host name on a different port and honours X-Forwarded-Host, so reverse
proxies work. If the interface still rejects a request:
Origin and Host – the cause is visible there;ALLOWED_ORIGINS, e.g.
ALLOWED_ORIGINS=https://docker.example.com, and restart the container;CSRF_PROTECTION=0.docker compose up -d restores the image
configured in the Compose file.docker logs), their result is stored and shown in the interface.ENTRYPOINT cannot be expressed as a single --entrypoint
argument in the generated rollback script; the script then contains a note.
Restoring through the interface is not affected.app/
main.py FastAPI routes, auth middleware, templates
auth.py password login, sessions, rate limit, origin check
config.py settings, data directories, per-container options
cron.py cron expressions: validation and next run
autocheck.py scheduler, lightweight check, email notification
version.py version number and build commit for the footer
docker_ops.py Docker API: list, pull, tagging, recreate, digest check
i18n.py German and English texts, language selection
operations.py update, snapshot, rollback and backup flows
jobs.py background jobs with live log
paths.py folder picker
templates/ Jinja2 + HTMX
static/ CSS, JS, bundled HTMX
tests/ pytest suite (runs without a Docker daemon)
README.md
Dockerfile python:3.12-alpine, non-interactive Uvicorn
docker-compose.yml socket, backups, config, example mounts
python3 -m pip install --target .devdeps fastapi uvicorn jinja2 python-multipart docker httpx pytest
PYTHONPATH=.devdeps:. CONFIG_DIR=/tmp/dui-config WEB_PASSWORD=dev \
BACKUP_DIR=/tmp/dui-backups BROWSE_DIR=/tmp \
python3 -m uvicorn app.main:app --reload --port 8080
PYTHONPATH=.devdeps:. python3 -m pytest -q tests
The tests cover login and permissions, page rendering, settings, folder picker, backup and restore, rollback scripts, cleanup, the job flow, cron parsing, digest checks and the scheduler. They do not need a Docker daemon.
Content type
Image
Digest
sha256:0b861dccc…
Size
24.3 MB
Last updated
1 day ago
docker pull kleinriese/docker-update-ui