- Python 68%
- TypeScript 22.3%
- Shell 5.6%
- CSS 3.5%
- Dockerfile 0.2%
- Other 0.3%
| containers | ||
| docs | ||
| scripts | ||
| stack | ||
| .gitignore | ||
| build-and-push.sh | ||
| build.sh | ||
| CLAUDE.md | ||
| README.md | ||
Drydock
🤖 AI-written project. This entire repository — code, documentation, and this README — is written by an AI agent (Claude Code) under human direction. Treat it accordingly: review before relying on it in production.
A self-hosted, Watchtower-style container update tool for a multi-host Docker homelab, with a central UI and the per-container control Watchtower lacks. It replaces Watchtower (archived December 2025) and What's Up Docker.
What it does
The running container is the source of truth. There is no git, no compose files, and no repo access anywhere in the runtime. A lightweight agent on each host reads the running containers from the Docker socket, reports them as inventory, and checks the registry for newer upstream images. On an approved update it pulls the new image and recreates the container with the same config on the new image, then prunes — exactly Watchtower's mechanism, plus a central multi-host UI.
Update modes
Each container's effective mode is its own override, or the global default (approve by
default):
| mode | on a detected update… |
|---|---|
auto |
pull + recreate automatically (Watchtower behavior) |
monitor |
notify only, never act |
approve |
land in the approval queue — apply when you say so |
Digest watch (same tag, new digest) is always on. Semver watch (newer version tags) is opt-in, per container with a global default.
Architecture
Two images are built from this repository:
ivooskamp/drydock— the management server: a single container running FastAPI that serves the built React UI. It has its own Postgres in the same Docker stack.ivooskamp/drydock-agent— a per-host agent with Docker socket access. It is a pure executor: it reports what runs and runs the jobs it is handed; it decides nothing.
All connections are outbound from agent to management — the management server never
connects to an agent. The web UI and full API are internal only; only the locked-down
/agent/ endpoint is exposed publicly (IP allow-list plus a per-agent token over public
HTTPS). This is what keeps an untrusted host (e.g. a seedbox) contained.
Repository layout
drydock/
├── CLAUDE.md ← context for the AI agent working on this repo
├── build.sh ← version-aware build wrapper: ./build.sh t | r
├── build-and-push.sh ← shared Docker build & push script
├── scripts/ ← version tooling (bump/check/set release version)
├── stack/ ← docker-compose for the management server and the agent
├── containers/
│ ├── drydock/ ← management server (FastAPI + built React)
│ │ └── app/version.py ← VERSION / BUILD source of truth
│ └── drydock-agent/ ← per-host agent
└── docs/
├── design-spec.md ← the full design specification (read this first)
├── deployment.md ← deployment notes
├── changelog-develop.md ← append-only development log
└── changelog.md ← release summary
Configuration
Only DATABASE_URL lives in environment variables. Everything else (registries, hosts,
update modes, poll interval, scope) is stored in the database and managed through the
settings UI. Migrations use Alembic.
Build & release
./build.sh t— bump the dev build number, build & push the:devimages, push thedevbranch../build.sh r— validate version state and release frommain: mergedev, tagvX.Y.Z, push, then recreate thedevbranch.
Versions are committed source state, not derived from git: a dev image is visibly
v0.1.0.22 rather than an ambiguous re-pushed v0.1.0.
Status
Early development (v0.1.0). See docs/design-spec.md for the full design and
docs/changelog.md for what has shipped.