Lightweight Docker container auto-updater. Watches running containers for newer images and recreates them in-place, preserving ports, volumes, networks, labels, and restart policies.
- Registry-first detection checks remote digests via HEAD requests (~50ms per image) and only pulls when an update exists
- Zero configuration out of the box. Mount the Docker socket and go. Every running container is watched by default
- Faithful recreation preserves the full container config across updates: ports, volumes, networks, env vars, labels, resource limits
- ~3 MB image built from scratch with a static Go binary, no runtime dependencies
docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/dirdmaster/isengardOr with Docker Compose:
services:
isengard:
image: ghcr.io/dirdmaster/isengard
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sockAll configuration is via environment variables.
| Variable | Default | Description |
|---|---|---|
ISENGARD_INTERVAL |
30m |
Check interval (Go duration format) |
ISENGARD_WATCH_ALL |
true |
Watch all containers; set false for opt-in mode |
ISENGARD_RUN_ONCE |
false |
Run a single check cycle, then exit |
ISENGARD_CLEANUP |
true |
Remove old images after a successful update |
ISENGARD_STOP_TIMEOUT |
30 |
Seconds to wait for graceful container stop |
ISENGARD_LOG_LEVEL |
info |
Minimum log level: debug, info, warn, error |
ISENGARD_SELF_UPDATE |
false |
Allow Isengard to update its own container |
Watch-all mode (default): every running container is watched. Exclude specific containers with a label:
labels:
- isengard.enable=falseOpt-in mode: set ISENGARD_WATCH_ALL=false and label only the containers you want watched:
labels:
- isengard.enable=trueIsengard checks remote digests directly via the registry v2 API (~50ms per image). For private registries, mount your Docker credentials so Isengard can authenticate these requests:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ~/.docker/config.json:/root/.docker/config.json:roWithout the mount, digest checks on private images will fail and Isengard falls back to pulling through the Docker daemon (which uses the host's own auth). The fallback works fine but skips the fast digest check.
Supports Docker Hub, GHCR, ECR, Quay, and self-hosted registries.
- Lists all running containers (filtered by mode and labels)
- For each container, sends a HEAD request to the registry to get the remote digest (~50ms)
- Compares the remote digest against the local image's
RepoDigests - If the digest differs, pulls the new image and recreates the container with the same configuration
- If the digest check fails (auth issues, unsupported registry), falls back to pull-and-compare by image ID
Set ISENGARD_SELF_UPDATE=true to let Isengard update its own container when a newer image is available. The self-update always runs last, after all other containers have been processed.
When a new image is detected, Isengard recreates its own container using the same stop/remove/create/start sequence it uses for every other container. Use restart: unless-stopped in your compose file so Docker restarts the new container if needed.
Isengard identifies its own container using multiple detection methods that work across Docker Compose, Swarm, cgroup v1, and cgroup v2 environments. No extra labels or configuration are needed beyond enabling the flag.
go install github.com/dirdmaster/isengard@latestOr build the Docker image:
docker build -t isengard .- Fork and clone, then run
bun installto set up git hooks via lefthook - Make sure you have Go 1.25+ installed
- Lefthook handles
go fmt,go vet,golangci-lint, andgo buildon pre-commit; tests run on pre-push - Use Conventional Commits:
feat:,fix:,chore:,refactor:,docs:,test:
See open issues for things to work on.