English · Español · Polski · 简体中文 · Deutsch · Français · Português (Brasil)
Container image update watcher — 23 registries, 20 notification and action providers.
Warning
Updating from an older release? Read the upgrade notes first. Three security-hardening fixes first shipped in 1.4.6 and run through the entire 1.5 line, so anyone updating from a release older than 1.4.6 is affected whatever version they land on (1.4.6, any 1.5.x, or later). They are not deprecations and have no grace period: OIDC now requires authorization_endpoint in your provider's discovery metadata, unauthenticated rate-limiting keys on the TCP peer address (shared bucket behind a reverse proxy), and HTTP-trigger proxy URLs must use http(s)://. See UPGRADE-NOTES.md before updating.
Warning
Updating to 1.6.0-rc.3 or later? More security-hardening fixes land with no grace period. An instance with no authentication configured — or with anonymous auth enabled but unconfirmed — now fails closed on upgrade, exactly like a fresh install: the container runs; protected API requests return 401; authentication discovery/status routes remain public; and /health returns 503. The SPA shell may still load, but it cannot read protected application data. Set DD_ANONYMOUS_AUTH_CONFIRM=true or configure DD_AUTH_BASIC_*/OIDC before upgrading. The session cookie is renamed connect.sid → drydock.sid, signing every existing user out once. HTTP notification triggers (plus the Hass webhook and registry icon fetches) now resolve hostnames through a guarded DNS lookup that blocks cloud-metadata/link-local targets and never follow redirects — set allowmetadata=true on a specific DD_NOTIFICATION_HTTP_* trigger if you legitimately need one. See DEPRECATIONS.md for full migration guidance.
- Documentation
- Quick Start
- Recent Updates
- Screenshots & Live Demo
- Why Drydock
- Features
- Supported Integrations
- Feature Comparison
- Migration
- Roadmap
- Star History
- Built With
- Community & Support
- CodesWhat Ecosystem
| Resource | Link |
|---|---|
| Website | getdrydock.com |
| Live Demo | demo.getdrydock.com |
| Docs | getdrydock.com/docs |
| Configuration | Configuration |
| Quick Start | Quick Start |
| Changelog | CHANGELOG.md |
| Deprecations | DEPRECATIONS.md |
| Roadmap | See Roadmap section below |
| Contributing | CONTRIBUTING.md |
| Code of Conduct | CODE_OF_CONDUCT.md |
| Governance | GOVERNANCE.md |
| Security Assurance | SECURITY-ASSURANCE.md |
| Security Policy | SECURITY.md |
| Issues | GitHub Issues |
| Discussions | GitHub Discussions — feature requests & ideas welcome |
Recommended: use a socket proxy to restrict which Docker API endpoints Drydock can access. This avoids giving the container full access to the Docker socket.
services:
drydock:
image: codeswhat/drydock
depends_on:
socket-proxy:
condition: service_healthy
environment:
- DD_WATCHER_LOCAL_HOST=socket-proxy
- DD_WATCHER_LOCAL_PORT=2375
- DD_AUTH_BASIC_ADMIN_USER=admin
- "DD_AUTH_BASIC_ADMIN_HASH=<paste-argon2id-hash>"
ports:
- 3000:3000
socket-proxy:
image: tecnativa/docker-socket-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- CONTAINERS=1
- IMAGES=1
- EVENTS=1
- SERVICES=1
- INFO=1 # Required for daemon identity detection (notification prefixes)
# Add POST=1 and NETWORKS=1 for container actions and auto-updates
healthcheck:
test: wget --spider http://localhost:2375/version || exit 1
interval: 5s
timeout: 3s
retries: 3
start_period: 5s
restart: unless-stoppedAlternative: sockguard socket proxy
sockguard is a default-deny Docker socket filter from the same CodesWhat ecosystem, with a preset built for drydock:
services:
drydock:
image: codeswhat/drydock
depends_on:
sockguard:
condition: service_healthy
environment:
- DD_WATCHER_LOCAL_HOST=sockguard
- DD_WATCHER_LOCAL_PORT=2375
- DD_AUTH_BASIC_ADMIN_USER=admin
- "DD_AUTH_BASIC_ADMIN_HASH=<paste-argon2id-hash>"
ports:
- 3000:3000
sockguard:
image: codeswhat/sockguard
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./sockguard.yaml:/etc/sockguard/config.yaml:ro
environment:
- SOCKGUARD_CONFIG_FILE=/etc/sockguard/config.yaml
healthcheck:
test: wget --spider http://localhost:2375/version || exit 1
interval: 5s
timeout: 3s
retries: 3
start_period: 5s
restart: unless-stoppedSee sockguard's app/configs/portwing.yaml preset for a starting sockguard.yaml (the same preset portwing ships in its own examples).
Alternative: quick start with direct socket mount
docker run -d \
--name drydock \
-p 3000:3000 \
-v /var/run/docker.sock:/var/run/docker.sock \
-e DD_AUTH_BASIC_ADMIN_USER=admin \
-e "DD_AUTH_BASIC_ADMIN_HASH=<paste-argon2id-hash>" \
codeswhat/drydock:latestWarning: Direct socket access grants the container full control over the Docker daemon. Use the socket proxy setup above for production deployments. See the Docker Socket Security guide for all options including remote TLS and rootless Docker.
Generate a password hash (
argon2CLI — install via your package manager):echo -n "yourpassword" | argon2 $(openssl rand -base64 32) -id -m 16 -t 3 -p 4 -l 64 -eOr with Node.js 24.7+ (no extra packages needed):
node -e 'const c=require("node:crypto");const s=c.randomBytes(32);const h=c.argon2Sync("argon2id",{message:process.argv[1],nonce:s,memory:65536,passes:3,parallelism:4,tagLength:64});console.log("argon2id$65536$3$4$"+s.toString("base64")+"$"+h.toString("base64"));' "yourpassword"Drydock v1.6 accepts only argon2id Basic auth hashes. Legacy
{SHA},$apr1$/$1$,crypt, and plain-text hashes are rejected; regenerate them before upgrading. Authentication is required by default. See the auth docs for OIDC, anonymous access, and other options. Anonymous access must be explicitly confirmed withDD_ANONYMOUS_AUTH_CONFIRM=trueon new and upgraded instances alike. Without it, an instance with no auth configured (or unconfirmed anonymous auth) starts fail-closed: protected API requests return401, public authentication discovery/status routes remain available, and/healthreturns503.
The image includes trivy and cosign binaries for local vulnerability scanning and image verification.
See the Quick Start guide for Docker Compose, socket security, reverse proxy, and alternative registries.
v1.7.0-rc.7 highlights
- Registry pagination now follows each registry's own cursor, preventing update checks from skipping pages or stopping early. (#927)
- Update execution stays successful when cleanup fails after the health gate, while SSE payloads are smaller and self-updates wait for active lifecycles before taking their exclusive gate. (#931, #942)
- Credential redaction now covers trigger, registry, debug-dump, and lookalike-host paths, preventing secrets from being logged or sent to attacker-controlled registry hosts. (#932)
- Compose rewrites now verify the runtime repository before writing, and agent pruning plus rollback-failure handling are covered safely. (#933)
- Header-authenticated requests no longer persist sessions, so Basic-auth polling does not grow the session store. (#935)
- The competitor comparison and roadmap are refreshed for 2026, keeping the release documentation current. (#936)
Full release notes in CHANGELOG.md.
v1.7.0-rc.6 highlights
- Two more gaps in agent container ownership are closed, on top of the earlier #904 fix — a brand-new container id had no ownership check at all, letting an agent claim a watcher name the controller itself owns; and the bulk ingestion paths (handshake, the watcher-snapshot fallback, on-demand
watch/watchContainer, and edgehandleContainerSync) reachedprocessAuthoritativeContainerwith no check in between, so an agent could still claim another agent's or the controller's container on its next routine snapshot. Both paths now enforce the same ownership checks the original fix added. - Registry pull auth, error-response leaks, and preview-error redaction are all tightened — thirteen registries (Hub, Custom, DHI, DOCR, Harbor, Gitea, Forgejo, Codeberg, Nexus, Artifactory, Alibaba CR, OCIR, IBM CR) authenticated for the version check and then pulled anonymously, because the pull-credential builder had no branch for a configured
authvalue; it now decodes that value the same way the lookup-credential builder already did, and a malformed value fails closed instead of silently returning nothing. Eight API handlers stopped interpolating a raw thrown message — which could carry anAuthorizationheader or a credentialed webhook URL — into a 500 response, routing instead through the existingsanitizePreviewErrorReasonscrubber, which now also redacts credentials embedded in a URL path segment (Telegram, IFTTT, and Discord webhook URLs), not just headers or userinfo. - Query-parameter validation is now consistent across the log, agent, and audit endpoints — a non-numeric
tailorsinceused to putNaNinto the ring-buffer read instead of being rejected, an empty?tail=was read as absent rather than invalid, and alimit/offsetwith a numeric prefix like?limit=25logsvalidated on its leading digits instead of failing; all three now reject anything that isn't a clean, whole integer. - Six UI defects are fixed — row selection never actually highlighted in seven views, because the shared data table declares
selectedKeybut every view was passing itactive-rowinstead; white text as low as 1.37:1 on the trigger test button and two avatars is flattened to a token that clears 4.5:1 in all twelve themes; the notification outbox and a container's full-page detail view each had their own race where the view rendered before its data resolved, both now guarded; two dashboard watchers missed every in-place SSE update because they watched a bare ref instead of a length- or fingerprint-aware source; and status text that rendered as raw English enum values in five places is now translated in all 16 locales. - 2109 strings that were still showing English source text are now actually translated, across all 16 non-English locales — large parts of the container list, the update and rollback dialogs, the search palette, and the notification outbox had silently fallen back to English regardless of the language selected. The weekly Crowdin sync also no longer reverts the six translated READMEs to English:
README.mdis no longer registered as a Crowdin source, and the translated READMEs are now hand-authored in-repo and asserted phrase by phrase at every cut. (#919) - Release and CI reliability fixes — the multi-architecture smoke build now retries around an open BuildKit race (moby/buildkit#7089) that could prepend the QEMU emulator path twice and kill a multi-arch build outright, and the release cut itself gains a full-build retry for the case where the first attempt produced no digest at all; the weekly DAST scan, which had never completed because ZAP alone consumed 39m46s of its 40-minute budget and starved Nuclei, now runs both scanners as separate parallel jobs; and the docs search, which returned roughly 1600 hits spread across five archived versions with the oldest changelog ranked first, now scopes to the version being read.
Full release notes in CHANGELOG.md.
v1.7.0-rc.5 highlights
- A security hardening pass closes five findings in Portwing and the debug/diagnostics surface — a malformed Portwing hello payload is now validated before parsing instead of throwing outside the callback error boundary, agent container ownership is enforced at the update/removal boundary, redaction now catches
*_PATvalues and credentials embedded in URLs (including scheme-relative ones), and the rejected-origin diagnostics path is rate-limited. (#904) - Dark themes meet the WCAG 2.2 contrast minimum — secondary/muted text, the tone colors, toast surfaces, and primary button labels are raised to clear 4.5:1 against the surfaces they're actually painted on, across all six dark themes. (#850, #865)
- Large fleets and slow clients no longer break the controller connection — an agent whose cached watcher replay exceeded 256 KiB could never reconnect, is now fixed by keeping the stream open for the authenticated handshake to supply state; SSE clients that fall behind now get bounded, drain-aware, in-order delivery instead of dropped or unbounded-memory writes; the system-log limiter no longer falls back to an empty identity; and an unsupported agent transport is now rejected at admission instead of failing later. (#904)
- Update and watcher lifecycle state stays accurate through restarts and teardown — startup recovery no longer marks an untouched container as updated, a restart no longer suppresses batch-completion events for updates still in flight, an update that never started is no longer reported as failed, a watcher torn down mid-setup can no longer be resurrected by a late callback, and concurrently parsed Docker event chunks no longer race a shared buffer. (#904)
- Backup, rollback, and container-list correctness fixes — backups carry a stable scoped identity instead of colliding on a shared container name, rollback restores the digest recorded with the backup instead of whatever a mutable tag now points at, concurrent digest scans no longer cancel each other, a successful container action no longer returns 500 when the follow-up refresh fails, and paginated container lists are sorted globally instead of only within a page. (#904)
- The Crowdin sync workflow no longer fails on non-default dev branches — a push to a
dev/vX.Ybranch that wasn't the newest one died with a checkout conflict because the base resolver always picked the highest dev branch regardless of which ref triggered the run; a push now targets its own branch directly. (run 33047712284)
Full release notes in CHANGELOG.md.
v1.7.0-rc.4 highlights
- WebSocket log streams work behind TLS-terminating proxies — with trust proxy enabled and
X-Forwarded-Protoabsent on the upgrade request, the origin check no longer falls back to the local socket's TLS state (plain HTTP behind TLS termination, so every browser connection 403'd); the protocol is treated as unknown and host validation is unchanged. Traefik forwards the upgrade's client-facing scheme aswssrather thanhttps(traefik/traefik#6388), which the origin check rejected outright, so the first fix alone still 403'd behind a default Traefik setup;ws/wssnow map tohttp:/https:for the Origin comparison. (#867, #868, #887) - Startup no longer crashes when the store volume forbids
chmod— the 1.6.0 permission tightening threw onEPERM, so mounts that rejectchmod(NFS/CIFS volumes, non-root containers) took the whole process down at boot and blocked 1.6.0 upgrades outright; it now warns and continues onEPERM/EACCES/ENOTSUP; a genuinely read-only volume (EROFS) still fails fast at startup, because nothing could be persisted there anyway. (#874, #886) - Debug dumps redact env var values, not names — env entries are
{key, value}pairs, and the redaction walker was matching the literal property namekeyagainst its sensitive-token rule, so a var likeHF_TOKENcame out with the name hidden and the secret in plain text; names now stay visible and values are redacted when the name matches a sensitive rule. (#875, #885) - Bare integer tags no longer outrank dotted versions — a build-counter tag like
168no longer coerces into a fake168.0.0that beats a real1.43.3, in both the suggested-tag badge and the actionableincludeTagsrecovery path, which now share one partition rule so they can't drift apart. (#859, #871) - Base images clear six HIGH OpenSSL CVEs — the
node:24-alpineandalpine:3.24digest pins and theopensslapk pin roll forward to OpenSSL 3.5.8-r0. (#881) - The demo site sends the full security-header set — the headers DAST flagged as missing on the demo surface are now sent. (#878)
- Containers that leave watch scope are pruned from the store and UI — a container excluded by
watchbydefaultbeing off, or by itsdd.watchlabel being removed, kept a stale record as long as it still inspected in Docker; stopped-but-watched containers keep their existing start-button behavior. (#869, #888)
Full release notes in CHANGELOG.md.
v1.7.0-rc.3 highlights
- Portwing edge tunnels carry non-JSON bodies — the controller's welcome frame advertises an
edge-response-body-b64capability and decodes base64-negotiated Docker response bodies (for example_ping's plain-textOK) from agents that support it, additive and capability-gated. (#852) - README badges read live — version, license, pull-count, and star badges now render from live shields.io endpoints instead of static images, and the star history chart ships as a themed light/dark pair that regenerates at the release cut instead of a cron. (#851, #844, #847)
- DAST and workflow-lint gates fail closed — the ZAP scans no longer ignore every warning, and the pre-push zizmor step errors with an install hint instead of silently skipping when the binary is missing. (#842)
- A daily monitor asserts
maincarries a release tag — a scheduled, read-only workflow goes red ifmain's HEAD is untagged. (#846) - Release-pipeline fixes — the rc.2 cut's CI break is fixed: a bad js-yaml override that broke Artillery load tests is reverted, and two Playwright waits are widened past the app's own operation budgets. (#829, #836)
Full release notes in CHANGELOG.md.
v1.7.0-rc.2 highlights
- Per-container action-policy resolution — the API and UI surface the resolved blocked/manual/auto state and winning trigger for every container, plus a new
dd.action.autolabel andAUTO=onautomode for manual-only access without automatic dispatch. - Breaking changes land this cycle —
DD_TRIGGER_*/dd.trigger.*are fully removed,trigger-excluded/trigger-not-includedbecome hard update blockers, the Home Assistant MQTT topic layout gains anagent/<name>segment by default,GET /api/auth/methodsreturns 410, andcurlis gone from the image. - Update-check correctness fixes — a registry error mid-check no longer reports "Up to date," a malformed container no longer zeroes out an entire agent inventory sync, and nested OCI image indexes now resolve to the real manifest. (#814)
- Dependency and self-update fixes — a rejected dependency member keeps its restart context, Compose refreshes no longer carry forward stale environment defaults, and update-policy overrides now survive drydock's own self-update. (#718, #736, #743)
- Security — closed a remote-property-injection path in the container list's URL query sync, and scoped the Grype image gate around a pending-upstream-fix Alpine CVE. (#750)
Full release notes in CHANGELOG.md.
v1.7.0-rc.1 highlights
- Dependency-aware updates — labels or Compose metadata build a validated dependency graph, preview exact update waves, and run updates or dependent restarts in deterministic order with cycle, failure, and stale-preview handling. (Discussion #219)
- Operator UX — installable PWA support, clickable named port links, live container uptime, keyboard shortcuts, and debounced first-seen container discovery.
- Breaking trigger migration —
DD_TRIGGER_*now fails startup and legacydd.trigger.include/dd.trigger.excludelabels no longer route work; useDD_ACTION_*,DD_NOTIFICATION_*, and their scoped labels. - Security and lifecycle hardening — bounded authentication, agent, log, WebSocket, and registry operations; sensitive command and hook values are redacted; Home Assistant discovery resynchronizes after startup and retires provider work without stale publishes. (#708)
Full release notes in CHANGELOG.md.
v1.6.0 highlights
- Portwing edge/agent transport matures — controller-owned native Docker checks/updates for Portwing 0.9.0+, continuous edge log streaming, Ed25519 request signing (v2), and agent-owned display names bound to their signing key. (#632, #637)
- Declarative update policy with a maturity stabilization gate — three-tier
dd.updatePolicy.*precedence, a live countdown to a held-back candidate's unlock time, and a dedicatedmaturity-clearednotification. (Discussion #307, Discussion #406) - Per-rule notification templates, bell preferences, and a new
container-unhealthyevent, plus bidirectional Home Assistant MQTT (Install button triggers a real update). (Discussion #205, Discussion #198) - Every major list view is responsive — one shared
DataTablewith a persisted table⇄card toggle across all ten list views, reflowing to cards below ~640px. (#498) /api/v1parity completes — the unversioned/api/*alias andWS /api/log/streamare removed (410 Gone); an opt-inDD_COMPAT_WUDCARDshim covers wud-card/Homepage. (Discussion #469)- Security hardening — anonymous access fails closed on upgrade (not just fresh installs), HTTP triggers are SSRF-hardened, WebSocket origin checks are full-origin, and the session cookie is renamed to
drydock.sid.
Full release notes in CHANGELOG.md.
v1.6.0-rc.13 highlights
- Digest comparison anchors on repo-matched candidates —
getOrderedRepoDigestsfilters a container'sRepoDigeststo entries whose repo component matches its own image reference before comparing, instead of trusting an arbitrary index-0 entry; a store already poisoned with a stale anchor self-heals. (#670) nanoidpinned to 3.3.18 across the root, app, apps/demo, apps/web, ui, and e2e workspaces (transitive override) for CVE-2026-67213 and, in e2e, CVE-2026-67214. (#673)- Star History chart is self-hosted — a new same-origin
/api/star-historyroute replaces the third-party embed that went down in a global outage, edge-cached with a fallback SVG on fetch failure. (#672) - Base-image CVE sweep —
node:24-alpinebumped to Node 24.19.0 and the vendoredaquasec/trivybuild-stage pin bumped to 0.73.0, clearing HIGH/MEDIUM CVEs in both. (#682) - Icon bundle alias resolution — the build-time icon extractor follows iconify alias chains and gains the missing Font Awesome brands collection, so renamed icons (like the Lucide-theme Audit icon) no longer ship as blank glyphs; a guard test pins every referenced icon into the bundle. (#683)
v1.6.0-rc.12 highlights
- Security dependency refresh —
brace-expansion5.0.9 (app/UI/e2e, CVE-2026-69152),ip-address10.3.1 (app runtime, CVE-2026-54272/-69192/-69198), andfast-uri4.1.2 (app/UI, CVE-2026-18446). (#659) - Maturity clock — the hot/mature badge resolves per-container
updatePolicy.maturityMinAgeDaysbefore the global threshold, matching the gate, and registry publish-date failures log atwarninstead of disappearing atdebug. (#604) - Agent registration grace — transient
agent-mismatch/no-update-trigger-configuredblockers soften on display surfaces while an agent's components re-register; admission stays fail-closed. (#605) - WS log streams + anonymous auth — log-stream WebSocket upgrades accept sessions when anonymous authentication is the registered mode. (#636)
- Explicit 501s — lifecycle actions on agent containers without controller Docker transport return 501 naming the cause instead of an ambiguous 404. (#637)
v1.6.0-rc.11 highlights
- Portwing transport — Portwing 0.9.0's exact
transport=docker-api,execution=controller,events=portwingmarker now routes native registry checks, single/batch updates, start/stop/restart, update previews, and backup rollbacks through authenticated Standard HTTP or Edge request/response/stream transport. Portwing remains the lifecycle-event source, and raw inventory cannot erase controller-enriched update results. (#632, #637, Portwing #76) - Notifications — Per-rule/per-provider title and body templates with live preview, plus audit-backed in-app bell categories and update severity thresholds.
- Dashboard — Zero-dependency CSS Grid replacement with mouse/touch reorder, bounded resize, responsive layouts, widget visibility, reset, and optional cross-device preference sync.
- Update policy — Declarative watcher/label/UI precedence, override/revert audit trail, maturity countdown/manual override, and pinned-tag informational visibility with a stacked current → newer Tag view.
- Container resources — The Resources column remains visible by default but can now be hidden persistently; Source, release-note, and registry shortcuts stay available from each row's More menu and from card footers.
- Performance & recovery — Per-poll tag-list deduplication, lighter aggregate projections, virtualized large log histories, immutable live-log rollover, auth-bootstrap timeout, complete preference migrations, and stale-chunk self-healing.
- v1.6 migrations enforced — WUD env/label aliases, legacy auth formats, obsolete watcher switches, template aliases, Kafka
clientId, and malformed token-only Hub/DHI public configs no longer run. The trigger-taxonomy aliases remain for one final error-level warning release.
Full migration guidance in DEPRECATIONS.md.
v1.5.2 highlights
- Recreation-safe update policy — Maturity gates, skipped tags/digests, and snoozes now survive container recreation for local and remote-agent workloads.
- Pinned-tag reliability — Fully pinned tags detect same-tag digest rebuilds again, while the UI can show a non-actionable newer same-family tag without changing update or trigger behavior.
- Rollback recovery — Failed replacement creation, network attachment, or startup now cleans up the candidate before restoring the original container, and repeated failures cannot cascade through nested rollback renames.
- Safer container recreation — Daemon-assigned MAC addresses are no longer pinned onto replacements, while explicitly configured primary-network MAC addresses remain preserved.
- Quieter local-image polling — Locally built or loaded images with no registry digest skip remote lookups instead of generating recurring authorization errors.
Full history in CHANGELOG.md.
Spot an update, see exactly what changes, apply it. Backup, health check, and rollback handled.
| Light | Dark |
![]() |
![]() |
Why look at screenshots when you can experience it yourself?
Fully interactive — real UI, mock data, no install required. Runs entirely in-browser.
Container images drift out of date silently. A base image patches a CVE, an app cuts a release, a tag moves. Unless you're watching every registry by hand, your running containers fall behind until something breaks or gets exploited.
Most tools force a tradeoff. The auto-updaters (Watchtower, Ouroboros) pull and restart with little visibility or control, and are now largely unmaintained. The dashboards (Portainer) manage containers but aren't built for update intelligence. Drydock is monitor-first: it watches 23 registries and tells you exactly what changed (major, minor, patch, or digest) before anything happens, then acts only when you let it. And it goes further than any of them. Trivy/Grype vulnerability scanning blocks unsafe updates, cosign verifies signatures, pre-update image backups roll back automatically on health-check failure, distributed agents cover remote hosts, and 20 notification and action integrations close the loop. The full update lifecycle, with a web UI and a REST API.
| Feature | Description | |
|---|---|---|
| 🔭 | Monitor-First Detection | Watches every running container and classifies each available update as major, minor, patch, or digest before anything happens. Nothing changes until you say so. |
| 📦 | 23 Registry Providers | Docker Hub, GHCR, ECR, ACR, GCR, GAR, GitLab, Quay, Harbor, Artifactory, Nexus, and 12 more. Public and private, cloud and self-hosted, with per-registry TLS and auth. |
| 🔔 | 20 Triggers | 17 notification channels (Slack, Discord, Telegram, Teams, SMTP, MQTT, ntfy, and more) plus Docker, Docker Compose, and Command actions, with per-event/provider templates, live preview, threshold filtering, and batch mode. |
| 🥊 | Update Bouncer | Trivy/Grype vulnerability scanning blocks unsafe updates before they deploy, with cosign signature verification and SBOM generation (CycloneDX and SPDX). |
| ↩️ | Image Backup & Auto Rollback | Pre-update image snapshots with configurable retention, automatic rollback on health-check failure, and one-click manual rollback from the UI. |
| 🪝 | Lifecycle Hooks | Pre and post-update shell commands via container labels, with per-hook timeouts and abort-on-failure control. |
| 🗂️ | Docker Compose Updates | Pull and recreate Compose services through the Docker Engine API with YAML-preserving image patching. |
| 🎛️ | Per-Container Policy | Regex tag rules and trigger routing use dd.* labels; maturity gates, skip/snooze/pin, and maintenance windows are stored via UI/API or watcher configuration. |
| 🛰️ | Distributed Agents | Monitor remote Docker hosts over SSE. Portwing 0.9.0+ agents work over inbound Standard HTTP or dial-out Edge WebSocket transport; Drydock 1.6.0-rc.11+ can run native registry checks and single/batch Docker updates controller-side through either authenticated path. Edge also carries continuous live logs with no inbound port required; DD_EXPERIMENTAL_PORTWING=false remains an emergency disable. |
| 🖥️ | Web Dashboard | Vue 3 UI with a zero-dependency customizable widget grid, responsive table/card views, live SSE updates, notification-bell controls, and per-container detail, logs, and stats. |
| 🔗 | REST API & Webhooks | Token-authenticated endpoints for CI/CD watch and update triggers, plus signed registry webhook ingestion for push events. |
| 🔐 | OIDC Authentication | Secure the dashboard with OpenID Connect (Authelia, Auth0, Authentik). All auth flows fail closed by default. |
| 📈 | Prometheus Metrics | Built-in /metrics endpoint with optional auth bypass for Prometheus and Grafana monitoring stacks. |
| 🌍 | 17 UI Locales | Fully wired translation system with English complete and 16 community-maintained locales synced through Crowdin, switchable in Config. |
| 🔒 | ReDoS-Immune Regex | Every user-supplied tag pattern compiles via re2js (a pure-JS RE2 port) for linear-time matching that can't be stalled by a catastrophic-backtracking pattern. |
Docker Hub · GHCR · ECR · ACR · GCR · GAR · GitLab · Quay · LSCR · Harbor · Artifactory · Nexus · Gitea · Forgejo · Codeberg · MAU · TrueForge · Custom · DOCR · DHI · IBM Cloud · Oracle Cloud · Alibaba Cloud
Docker · Docker Compose · Command
Apprise · Discord · Google Chat · Gotify · HTTP · IFTTT · Kafka · Matrix · Mattermost · MQTT · MS Teams · NTFY · Pushover · Rocket.Chat · Slack · SMTP · Telegram
Anonymous (opt-in via DD_ANONYMOUS_AUTH_CONFIRM=true) · Basic (username + password hash) · OIDC (Authelia, Auth0, Authentik). All auth flows fail closed by default.
Trivy- or Grype-powered vulnerability scanning blocks unsafe updates before they deploy. Includes cosign signature verification and SBOM generation (CycloneDX & SPDX).
How does drydock compare to other container update tools?
✅ = supported ❌ = not supported
⚠️ = partial / limited ? = unconfirmed † = archived, no longer maintained
| Feature | drydock | WUD | Diun | Watchtower † |
|---|---|---|---|---|
| Actively maintained | ✅ | ✅ | ✅ | ❌ |
| Web UI / Dashboard | ✅ | ✅ | ❌ | ❌ |
| Auto-update containers | ✅ | ✅ | ❌ | ✅ |
| Docker Compose updates | ✅ | ✅ | ❌ | |
| Semver-aware updates | ✅ | ✅ | ❌ | |
| Digest watching | ✅ | ✅ | ✅ | ✅ |
| Update threshold filtering (major/minor/patch/digest) | ✅ | ✅ | ❌ | ❌ |
| Dependency-aware update ordering | ❌ | ❌ | ❌ | |
| Pending-approval queue | ❌ | ❌ | ❌ | |
| Image backup & rollback | ✅ | ❌ | ❌ | ❌ |
| Lifecycle hooks (pre/post) | ✅ | ❌ | ❌ | ✅ |
| Vulnerability scanning | ✅ | ❌ | ❌ | ❌ |
| Audit log | ✅ | ❌ | ❌ | ❌ |
| RBAC / multi-user roles | ❌ | ❌ | ❌ | ❌ |
| OIDC / SSO authentication | ✅ | ✅ | ❌ | ❌ |
| Trigger / notification channels | 20 | 17 | 17 | ~20 |
| MQTT / Home Assistant | ✅ | ✅ | ❌ | |
| Registry providers | 23 | 12 | ||
| REST API | ✅ | ✅ | ||
| Webhook API for CI/CD | ✅ | ❌ | ❌ | ✅ |
| Prometheus metrics | ✅ | ✅ | ✅ | ✅ |
| Distributed agents (remote) | ✅ | ❌ | ||
| Container grouping / stacks | ✅ | ✅ | ❌ | ❌ |
| Container start/stop/restart/update | ✅ | ❌ | ❌ | ❌ |
| Container log viewer | ✅ | ❌ | ❌ | ❌ |
Watchtower was archived in December 2025 and its last release was v1.7.1 (November 2023). An unofficial community fork, nicholas-fedor/watchtower, is still actively released.
| Feature | drydock | Arcane | Komodo | Dockhand |
|---|---|---|---|---|
| Actively maintained | ✅ | ✅ | ✅ | ✅ |
| Web UI / Dashboard | ✅ | ✅ | ✅ | ✅ |
| Auto-update containers | ✅ | ✅ | ✅ | ✅ |
| Docker Compose updates | ✅ | ✅ | ✅ | ✅ |
| Semver-aware updates | ✅ | ❌ | ❌ | ✅ |
| Digest watching | ✅ | ✅ | ✅ | ✅ |
| Update threshold filtering (major/minor/patch/digest) | ✅ | ❌ | ||
| Dependency-aware update ordering | ✅ | ✅ | ? | |
| Pending-approval queue | ❌ | ❌ | ❌ | |
| Image backup & rollback | ✅ | ❌ | ❌ | ❌ |
| Vulnerability scanning | ✅ | ✅ | ❌ | ✅ |
| Audit log | ✅ | ✅ | ✅ | |
| RBAC / multi-user roles | ❌ | ✅ | ✅ | |
| OIDC / SSO authentication | ✅ | ✅ | ✅ | ✅ |
| Trigger / notification channels | 20 | 11+ | 5 | 15+ |
| MQTT / Home Assistant | ✅ | ❌ | ❌ | ✅ |
| Registry providers | 23 | |||
| Prometheus metrics | ✅ | ❌ | ❌ | ✅ |
| Distributed agents (remote) | ✅ | ✅ | ✅ | ✅ |
| Container grouping / stacks | ✅ | ✅ | ✅ | ? |
Compiled from each project's public documentation and repositories, 2026-08-29. Contributions welcome if any information is inaccurate.
Migrating from WUD (What's Up Docker?)
Drydock v1.6 no longer loads WUD_* environment variables or wud.* labels at runtime. Rewrite them before starting the upgraded service; persisted state still migrates automatically. Use docker exec -it drydock node dist/index.js config migrate --dry-run to preview, then docker exec -it drydock node dist/index.js config migrate --file .env --file compose.yaml to rewrite configuration to DD_* and dd.* naming.
Version themes & highlights
This direction covers at least the next twelve months, through August 2027. High-level themes only; see CHANGELOG.md for per-release detail.
| Version | Theme | Highlights |
|---|---|---|
| v1.3.x ✅ | Security & Stability | Trivy scanning, Update Bouncer, SBOM, 7 new registries, 4 new triggers, re2js regex engine |
| v1.4.x ✅ | UI Modernization & Hardening | Tailwind 4 + custom components, 6 themes, Cmd/K palette, OpenAPI 3.1, compose-native YAML updates, dual-slot scanning, OIDC hardening |
| v1.5.0 ✅ | Observability & i18n | trigger taxonomy split (DD_ACTION_*/DD_NOTIFICATION_*), WebSocket log viewer, dashboard customization, resource monitoring, notification outbox + DLQ, security scan digest, 17 locales, SSE Last-Event-ID replay, edge agent dial-out with Ed25519 auth (experimental, DD_EXPERIMENTAL_PORTWING=true) |
| v1.5.1 ✅ | Security & Maintenance | GCR/GAR pull-auth fix, registry TLS completion (M-2), hook env-var injection hardening, DD_SESSION_SECRET__FILE support, debug-dump credential redaction, secret-file permission check, maturity gate deadlock fix, full UI translatability + community translations, maintenance-window auto-apply gate, container uptime display, Tag/Version column split surfacing software version (OCI label, with dd.inspect.tag.path dual-write + opt-in dd.inspect.tag.version-only routing), opt-in compose mount-prefix matching, ${currentReleaseNotes} template var |
| v1.5.2 ✅ | Policy & Pinned-Tag Reliability | Recreation-safe maturity/skip/snooze policy retention, pinned-tag digest rebuild detection and informational same-family insights, rollback-candidate cleanup, rollback-cascade prevention, explicit-MAC preservation, and local-image registry-skip behavior |
| v1.6.0 ✅ | Notifications, Policy & Release Intel | Per-rule/per-trigger notification templates with live preview, notification-bell preferences, cross-device preference sync, zero-dependency custom dashboard grid (#281), declarative update policy (#320), maturity stabilization countdown + immediate candidate visibility + manual override (#406), actionable Update Status panel and global notify / manual / auto update mode (#325), watcher/imgset/container tag-policy inheritance plus stacked current → newer pinned-tag visibility (#498), standardized 44px Source / release notes / registry resource actions across table, cards, and details (#295), health-status event notifications (#198), bidirectional Home Assistant MQTT, responsive table/card list views, Trivy/Grype/both scanning across command or pinned Docker-worker backends, scanner asset pull/warm controls, off-heap deduplicated SBOM storage, Trivy long-scan correctness (#490), trigger-taxonomy migration warnings, v1.6 compatibility removals, docs/API hygiene, and /api → /api/v1 migration completion with an opt-in wud-card/Homepage compatibility shim (DD_COMPAT_WUDCARD). |
| v1.7.0 | Smart Updates & UX | Dependency-aware ordering (#219), selective bulk updates (#232), per-action update policy (#511), image prune, static image monitoring, unified maturity/update-age clock, clickable port links, keyboard shortcuts, PWA, dark-theme contrast pass (WCAG 2.2) (#850, #865), DD_TRIGGER_* removal (end of the v1.5.0 deprecation window), curl removed from the image |
| v1.8.0 | Fleet Management & Live Config | YAML config, live UI config, volume browser, parallel updates, SQLite store migration, Home Assistant update progress + per-container devices (#210), locally-built images watched against a declared upstream base (#897), scoped rotatable API keys (static bearer tokens for HA/dashboard integrations, #469), per-update approval queue |
| v2.0+ | Platform Expansion & Beyond | Swarm/Kubernetes watchers, GitOps, health gates, canary deploys, web terminal, RBAC, LDAP/AD, native Podman provider beyond the Docker-compatible API, CLI, Wolfi hardened image, socket proxy |
Real-time chat and early support: CodesWhat Discord
Bugs and concrete feature requests go to GitHub Issues; open-ended questions, ideas, and show-and-tell go to GitHub Discussions; real-time chat happens on the CodesWhat Discord.
Thanks to the users who helped test v1.4.0 and v1.5.0 release candidates and reported bugs:
@RK62 · @flederohr · @rj10rd · @larueli · @Waler · @ElVit · @nchieffo · @begunfx · @Ra72xx
| Tool | Role |
|---|---|
| drydock | Container update monitoring — web UI and notification engine |
| portwing | Remote Docker agent — secure socket-level access from Drydock or standalone |
| sockguard | Docker socket proxy — default-deny allowlist filter protecting the socket |
These three tools are designed to layer: sockguard filters the socket, portwing exposes it remotely, and drydock monitors and acts on container state.
See portwing's COMPATIBILITY.md for the full compatibility matrix across all three tools.


