Updates
Release update notes and feature highlights, with direct links to current configuration and API docs.
Unreleased
v1.7.0-rc.16 Highlights — September 15, 2026
Home Assistant discovery and state publishing now agree for agent containers
with the existing HASS_AGENTTOPICSEGMENT=true default. Canonical container
names and plain MQTT topics are unchanged. Thanks to
@depuits for the reproduction details in
discussion #1201.
This candidate also refreshes dependencies and the Alpine timezone package pin, restores privacy-preserving website referrer reporting, and fixes Crowdin branch selection and dependency guards. The runtime changes start a fresh seven-day soak. Old retained MQTT payloads are not deleted automatically; check for other publishers before removing obsolete state. See CHANGELOG.md for the full release notes.
v1.7.0-rc.15 Highlights — September 10, 2026
Disconnected edge agents can no longer publish an in-flight component over their reconnected replacement. Retired registrations clean up only their own components, and disconnected proxy, log, delete, and exec requests fail immediately without allocating request state or sending frames. These runtime changes require a fresh seven-day soak for this candidate. See CHANGELOG.md for the full release notes.
v1.7.0-rc.14 Highlights — September 8, 2026
- Rollback now applies the same runtime-config sanitization as an update, so an entrypoint or command introduced by the newer image is not copied onto an older image that cannot run it. Tag family matching requires an exact variant suffix, with a digit-only precision exception; a trailing dot no longer qualifies (#1134).
- Maintenance image labels now name the release source SHA instead of the workflow's main-branch commit (#1126).
Full release notes are in CHANGELOG.md.
v1.7.0-rc.13 Highlights — September 8, 2026
- Containers on a floating tag from before v1.5.0-rc.17 could stay marked Current forever, even with a newer digest available —
image.digest.watchis now re-derived every scan instead of being fixed at first discovery (#1108). - The weekly ZAP full scan of getdrydock.com hit its 60-minute job timeout on every run and never produced a report — the scan step now caps the spider at 10 minutes and the active scan at 35, leaving room for the passive scan and report inside the job budget (#1080).
v1.7.0-rc.12 Highlights — September 6, 2026
- The demo site was missing
Cross-Origin-Opener-Policy— the weekly DAST scan failed on ZAP rule 90004 every run;apps/demo/vercel.jsonnow sendssame-originnext to the existingCross-Origin-Embedder-Policyheader (#1050). - The arm64 pass of the release image-arch check failed on every multi-platform cut —
scripts/check-image-arch.shnow resolves each platform's own manifest digest out of the multi-arch index before probing it, instead of reusing a single index-digest reference docker's classic image store can't hold two platform variants under (#1046). - Moving a container to another agent, or removing an agent from config, no longer resets its update policy — the agent-removal prune and the agent's own stale-container prune now pass
identityChangeExpected: trueand stash the departing record's snooze, maturity mode, and skipped tags under its Docker id, the same way the startup prune already did (#1050). - A manual "check now" landing mid-scan could fire the same notification twice — the dashboard, the API, a webhook, and the controller polling an agent all route through the same single-flight scan orchestration the cron schedule uses, reporting a coalesced call via
result.coalescedor anX-Drydock-Watch-Coalescedheader instead of starting an independent scan (#1050). - A
once=truenotification whose trigger never replied held its dedup key forever — each reservation now expires on its own timer, four times the handler timeout, and logs a warning naming the key if nothing released it first (#1050). - DR-121: the session store and the main store wrote the same
/store/dd.json, and whichever saved last erased the other's data — the session store now writes to its own sibling file,dd-sessions.jsonby default, and the main store drops a staleSessionscollection left behind by an older build (#1063). - The agents page's paired Gitea registry example had the controller talking HTTPS to an agent serving plain HTTP — the agent block now mounts its own certificate and sets
DD_SERVER_TLS_ENABLED, so the example connects as written (#1042).
v1.7.0-rc.11 Highlights — September 5, 2026
- OIDC login no longer bounces back to the login page — the service worker's navigation fallback answered every document navigation except
/api/from the cached app shell, so the identity provider's callback never reached Express for its code exchange; the fallback now skips every server-owned route (/api,/auth/,/health,/metrics) (#1016). - A controller with its own
localwatcher no longer refuses every container an agent reports under a same-named watcher — ownership of a container with no store row is now decided by what the controller's own watchers have actually enumerated, not by a watcher-name collision (#1018). - A rollback of a compose-managed container no longer redeploys the update it was undoing — the compose recreate now passes the caller's image through to the runtime refresh instead of re-deriving one from the container's update candidate, automatic rollback hands the health monitor the whole container instead of just its id and name, and the backup image is pulled, digest-pinned when the record carries one, before anything is stopped or removed (#1023, #1029).
- The published arm64 image is a real arm64 build again — both base-image pins are back on multi-arch image index digests instead of a single amd64 manifest, and the release now refuses to sign, tag, or promote an image whose binaries don't match the platform it's published under, closing the
exec format errora Raspberry Pi hit under #1021 (#1024). - A container moved to an agent no longer stays stranded when the controller's local watcher is off — the startup prune that clears stale controller-owned records now also runs when no local watcher was ever configured, so the agent's report is no longer refused as controller-owned (#1037).
- A container handed from the controller to an agent no longer loses its update policy — the startup prune now stashes the snooze, maturity mode, and skipped tags under the container's Docker id, which the move leaves alone, instead of a key the incoming record could never look up (#1037).
- One local watcher failing to register no longer deletes its containers' records when a second watcher registers fine — the prune now keeps any record whose watcher is still configured, whether or not it registered this run, and waits for every registration to settle before reading the registry (#1037).
- The agents page now says registries have to be configured on every agent, not just the controller — a worked Gitea example shows both sides (#1010).
- The agents page's registries example now mounts the CA file it references — instead of pointing
DD_AGENT_REMOTE1_CAFILEat a path the compose service never volumed in (#1037). - The watchers page now says the maintenance window gates the entire scheduled scan, not just installing an update — a closed window leaves new containers invisible, container state stale, and update notifications deferred until it reopens; a manual scan still bypasses it, same as a manual update (#1010).
v1.7.0-rc.10 Highlights — September 4, 2026
- Batch and digest
once=trueeligibility now takes the same reservation the simple notification path takes — the reservation added for #972 covered the simple path only, so a manual single-container scan overlapping a cron scan passed a plain history read in both evaluations and sent the same candidate twice; batch and digest eligibility now reserve synchronously, the batch releases every reservation it took in a finally, the digest flush holds one for exactly the results it is sending and skips and evicts a substituted result an earlier flush already sent, and the batch retry buffer no longer carries an unreserved entry to the trigger (#998). - A deregistered watcher no longer warns about a cron-scan deadline it no longer owns — the deadline timer and a cancel handle now live on the watcher state, so deregistering a watcher while its
watch()is stalled clears the timer and settles the race with its own sentinel, every caller resolves to an empty result with no deadline warning, the per-scan cleanup is identity-guarded so a late settlement cannot clear a newer scan's handle, and a scan requested after teardown is refused outright, which also covers thejust-debouncedocker-events timeout that still fires up to five seconds after the watcher is gone (#998). - The getting-started guide says where hook scripts run, and the agent registry-lookup fix credits its contributor — hooks run inside the Drydock container with
DD_HOOKS_ENABLED=true, so a path that only exists on the host or in the updated container fails (#996). - The same hooks paragraph names the pre-hook abort as the default and
dd.hook.pre.abort=falseas the opt-out — the sentence had described the abort as unconditional (#1001).
v1.7.0-rc.9 Highlights — September 3, 2026
watchFromCron()is now single-flight — overlapping scans on a large fleet no longer fire the same trigger multiple times for one update; a request while a scan is running records that a rescan was requested instead of starting a second one, exactly one follow-up scan runs once the current scan finishes, and an in-flight scan that never settles is raced against a deadline so it can't wedge later cron ticks either, reported by @tarzan77cz (#979, #972).- The
once=truenotification-history key is now stable under a rate-limited digest lookup — the tag-kind hash no longer swaps between the digest and the image'screatedtimestamp when a registry rate-limits the digest lookup, so an update already announced does not re-fire hours later, and concurrent evaluations of the same history key are now atomic so overlapping scans can't both pass the check and both send, reported by @tarzan77cz (#979, #972). - Two deprecation banners and the marketing site's Get Started snippets are corrected — the UI's
legacyConfigBodyandcurlHealthcheckBodybanners (all 17 locales) now say the removedDD_TRIGGER_*env vars and curl-based healthcheck override are already gone instead of pointing at a removal deadline that already passed, and the marketing site's quick-start and hardened compose presets now configure authentication (DD_ANONYMOUS_AUTH_CONFIRM=trueon the quick preset) and a/storevolume so the deployed instance actually becomes healthy (#988). - A docs audit corrects the README, DEPRECATIONS.md, and the configuration/triggers/registries/API/monitoring/agents docs against this tree's actual code — compose secret escaping and
/storevolumes, mistranslated provider names and a backwards roadmap link, a wrong deprecation removal-status label plus four missing entries, hook/threshold/rollback claims that didn't match the code, missing trigger template variables and shared config keys, a nonexistent registry routing mechanism and a missing rate-limit row, a stale audit action list and log-filter semantics, and health-gate/signing-key/health-check claims that didn't match the code (#988).
v1.7.0-rc.8 Highlights — September 3, 2026
- Update pull-to-deploy identity is now pinned on both paths — the Docker-native and Compose update executors inspect the pulled image once, match its
RepoDigestsagainst the reference's own repository, bind an immutablerepo:tag@digestreference, and run signature verification, vulnerability scanning, SBOM generation, and the replacement create against that pinned reference, closing the registry-retag window that previously let a mutable tag point somewhere else between the gate and the deploy (#961, #952). - Self-update and agent snapshot handling survive more edge cases — self-update no longer rolls back a health-verified replacement when removing the old controller container fails, treating a missing old container as already cleaned up; and the watcher-snapshot handler now refuses to prune on an empty container list the same way its sibling entry points already do, closing the gap left after the cold-start race and the
watch()fix, with the test that had frozen the old pruning behavior in place now inverted (#951, #929, #386, #922, #565). dd.registry.lookup.imagenow applies to agent-reported containers —buildContainerReport()applies the registry lookup label (and its legacydd.registry.lookup.urlalias) the same way the local watcher already does, so containers reported by a controller-Docker-transport agent such as Portwing honor the override instead of silently checking a mirror's own sparse tag copy (#956, #336).- Two more agent-config and registry-state defects are fixed —
DD_AGENT_ALLOW_INSECURE_SECRETis excluded before the flatdd.agentprefix parser runs, so it no longer produces a phantom agent namedallow; and a container whose stored registry name isunknownnow re-enters the repair path on refresh instead of staying stampedunknownuntil it is recreated, both reported by @depuits (#954, #955, #945). - Debug dumps redact more provider-specific credential fields — Apprise service URLs, Rocket.Chat user IDs, and Telegram chat IDs are now redacted alongside the existing ordinary
parse.urlsconfiguration flags (#953). - Four findings from the rc.6 QA sweep are fixed — the Trivy supply-chain advisory now matches the image's actual bundled version and digest, backed by a coupling test; the site has a real 404 page in the marketing shell instead of Next's bare fallback; the audit log's search count is now scoped to the fetched page instead of comparing against the server's unfiltered total; and the servers detail panel re-resolves its selection after every refetch instead of showing stale data behind its own Refresh button (#928).
v1.7.0-rc.7 Highlights — August 29, 2026
- Registry tag discovery now handles real OCI pagination — Drydock follows each registry's opaque
Linkcursor, validates pagination URLs stay on the registry origin, caps runaway listings, and applies stricter rate limiting to ECR Public. Repositories such aspublic.ecr.aws/supabase/postgrescan now resolve tags beyond the first page (#927). - Update operations keep healthy replacements healthy — cleanup failures after the health gate no longer trigger rollback, lifecycle SSE events use the compact container projection instead of shipping vulnerability and SBOM payloads, self-update helpers inherit the root-mode flags and restore renamed containers after an early exit, and duplicate caller-supplied operation IDs return 409. Self-updates now wait for active lifecycles before taking the exclusive gate, so later updates queue behind them (#931, #930, #942).
- Secrets are scrubbed across triggers, debug dumps, registries, and commands — HTTP bearer credentials, compound environment names, URL credentials, and provider fields are redacted; debug dumps use the same sanitized container data as the API, are audited and rate-limited; registry host matching rejects lookalikes before credentials are sent; and command or lifecycle-hook scalar values are protected from splitting, globbing, and leading options (#932).
- Compose, agent pruning, and logs close four correctness gaps — compose rewrites reject a repository mismatch even when reconciliation is off, repeat the check under the file lock against the freshly read multi-file chain, and create backups only after it passes; foreign container IDs are filtered before agent pruning reaches
processAuthoritativeContainer; ESC-prefixed and C1 CSI sequences plus the remaining C1 controls are removed correctly bysanitizeLogParam; androllback-failedoutcomes retain their intended lifecycle status (#933, #938). - Header-authenticated requests no longer create sessions — requests using an
Authorization: Basicheader authenticate without persisting an unreachable session, while cookie authentication continues to persist and reuse sessions (#935). - Comparison and roadmap documentation reflect the current product — the competitor tables now cover current update managers and management platforms, including Komodo's maintenance windows and limited dry-run; shipped capabilities and audited feature counts are corrected, including Drydock's 11-registry lead over WUD; and rotatable API keys plus the per-update approval queue are placed on the v1.8 roadmap (#936, #938).
v1.7.0-rc.6 Highlights — August 29, 2026
- 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.
v1.7.0-rc.5 Highlights — August 27, 2026
- 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).
v1.7.0-rc.4 Highlights — August 26, 2026
- 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).
v1.7.0-rc.3 Highlights — August 23, 2026
- Portwing edge tunnels carry non-JSON Docker response bodies — the controller's welcome frame now advertises an
edge-response-body-b64capability and decodes base64-negotiated response bodies (for exampleGET /_ping's plain-textOK) from agents that support it, falling back to the existing path otherwise. Additive and capability-gated, no protocol-version bump (#852). - README badges read live instead of being hand-bumped — the version, license, pull-count, and stars badges in the English README and all six translations now render from live shields.io endpoints instead of static images, and the star history chart ships as a
<picture>block with theme-matched light and dark SVGs that regenerate from the release-cut workflow instead of a cron (#851, #844, #847). - DAST and workflow-lint gates fail closed — the ZAP scans no longer pass
-I(which told ZAP to ignore every warning), so findings now actually fail the gate, 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 whenmain's HEAD is untagged, deliberately not a required PR context since a promotion merge is untagged by definition until the cut lands (#846). - The release pipeline no longer trips over its own test infrastructure — the rc.2 cut failed on
main's push-triggered CI: a global js-yaml v5 override broke Artillery's load-test jobs, reverted to the v3 range Artillery actually supports (#829), and two Playwright waits were budgeted tighter than the backend operations they cover, so promotion runs raced the app's own state machine, fixed by raising the ceilings past the app's budgets (#832, #836).
v1.7.0-rc.2 Highlights — August 20, 2026
- Per-container action-policy resolution — the API and UI now surface the resolved
blocked/manual/autostate and winning trigger for every container and candidate action trigger, plus a newdd.action.autolabel andAUTO=onautomode so a container can opt into manual-only access without picking up automatic dispatch (spec-6.0.1-action-policy.md, slice 5). - BREAKING changes take effect this cycle — the legacy
DD_TRIGGER_*prefix anddd.trigger.*labels are fully removed,trigger-excluded/trigger-not-includedbecome hard update blockers ending the v1.5.0–v1.6.x soft-severity grace period, the Home Assistant MQTT topic layout now scopes multi-agent deployments under anagent/<name>segment by default,GET /api/auth/methodsreturns 410, andcurlis gone from the Docker image in favor of the compiled healthcheck binary. - Dependency-graph and admission fixes — a rejected dependency member no longer loses its restart context in wave 0, and Docker Compose refreshes no longer carry forward a stale image-inherited environment default (#718, #736).
- Self-update state survives itself — the
updatePolicyRetentionCachethat lets a recreated container inherit its predecessor's policy overrides now persists write-through to the store instead of being silently dropped on drydock's own self-update restart (#743). - Update checks stop reporting false confidence — a registry error mid-check no longer reports "Up to date," a single malformed container no longer zeroes out an entire agent inventory sync, and nested OCI image indexes (Buildx SBOM/provenance builds) now resolve to the real manifest instead of erroring (#814).
- Security — closed a remote-property-injection path in the container list's URL query sync, and scoped the Grype image gate around CVE-2026-14456 pending an upstream Alpine fix (#750).
- Everything else — Greptile second-opinion review is available behind a label,
release-cut.ymlcan cut a maintenance patch from a retired dev branch, the container SBOM attestation is now documented in the verification guide, the trivy qlty plugin gave way to Grype, the login screen's oversized logo asset was fixed, and the star-history chart moved to a committed SVG.
v1.7.0-rc.1 Highlights — August 14, 2026
- Dependency-aware updates — labels or Compose metadata resolve into deterministic update waves, with preview-bound confirmation, restart-only dependents, cycle handling, and explicit skipped-dependency results (Discussion #219).
- Operator UX additions — installable PWA support, clickable named port links, live container uptime, keyboard shortcuts, and a configurable settling window for newly discovered containers.
- Trigger migration reaches removal —
DD_TRIGGER_*now fails startup and legacydd.trigger.include/dd.trigger.excludelabels no longer route work; use the action/notification-specific replacements. - Security and lifecycle hardening — authentication, agent requests, logs, WebSockets, and registry requests have explicit resource bounds; sensitive command/hook values are redacted; Home Assistant discovery now resynchronizes after startup and drains retiring provider work without stale publishes (#708).
- Portwing controller and Edge reliability — Edge refresh cadence is configurable with
DD_PORTWING_POLL_INTERVAL;exec_end.reasonreaches internal consumers without leaking sessions; and controller-owned Docker inventory/events normalize against configured registries so native watch-now checks no longer round-trip through Portwing's intentional 501 registry stub (#688, #635, #687). - Registry metadata failures stay visible — optional manifest/blob responses with status 301, 302, 303, 307, or 308 can omit
createdwithout discarding a resolved digest, while exhausted 4xx, 5xx, network, and non-object failures now propagate instead of silently becoming a successful result with unstable metadata (#606). - First-party star history — the same-origin
/api/star-historyprovider serves allowlisted Drydock, Sockguard, and Portwing charts without relying on a third-party tracker.
v1.6.0 Highlights — August 11, 2026
Consolidates the 1.6.0-rc.1 … 1.6.0-rc.13 prereleases into one GA release. The
biggest themes: Portwing edge/agent transport maturing into a first-class citizen,
a declarative three-tier update policy with a maturity stabilization gate,
per-rule notification templates and a new health-status event, every major list
view going responsive with a consistent table/card toggle, /api/v1 parity with
the unversioned /api/* alias removed, and fail-closed security hardening
throughout. See CHANGELOG.md
for the full release notes.
v1.6.0-rc.13 Highlights — August 8, 2026
- Digest comparison anchors on repo-matched candidates —
getOrderedRepoDigests(app/watchers/providers/docker/docker-helpers.ts) filters a container'sRepoDigestsentries to the ones whose repo component matches its own image reference before comparing, instead of trusting an arbitraryRepoDigests[0]; a store already poisoned with a stale digest anchor self-heals on the next cycle (#670). nanoidpinned to 3.3.18 across the root, app, apps/demo, apps/web, ui, and e2e workspaces (transitive override ofpostcss/artillery) for GHSA-2v37-7h3g-55p8 (CVE-2026-67213) and, in e2e, GHSA-28wg-ghj8-5hjv (CVE-2026-67214) (#673).- Star History chart is now self-hosted — a new same-origin
GET /api/star-historyroute on the website replaces the third-party embed that went down in a global outage, edge-cached six hours with stale-while-revalidate and a short-lived fallback SVG on fetch failure (#672). - Base-image CVE sweep —
node:24-alpinebumped to Node 24.19.0 (clearing 3 HIGH + 5 MEDIUM CVEs from Node's July 29 2026 security release) and the vendoredaquasec/trivybuild-stage pin bumped from 0.72.0 to 0.73.0 (clearing 4 HIGH / 6 MEDIUM CVEs in its vendored Go dependencies) (#682).
v1.6.0-rc.12 Highlights — August 4, 2026
- Security dependency refresh —
brace-expansionadvanced to 5.0.9 across the app, UI, and e2e workspaces (CVE-2026-69152),ip-addressto 10.3.1 in the app runtime (CVE-2026-54272, CVE-2026-69192, CVE-2026-69198), andfast-urito 4.1.2 in app and UI (backslash-authority host confusion, CVE-2026-18446) (#659). - Maturity badge and gate agree again — the hot/mature badge now resolves each container's own
updatePolicy.maturityMinAgeDaysbefore the globalDD_UI_MATURITY_THRESHOLD_DAYS, matching the gate, and registry publish-date lookup failures (including GHCR/LSCR auth errors) log atwarninstead of vanishing atdebug(#604). - No more transient "Agent Mismatch" — the container list and SSE stream soften
agent-mismatch/no-update-trigger-configuredto soft blockers while an agent's components are mid-(re)registration; update admission stays fail-closed throughout (#605). - WebSocket log streams accept anonymous-auth sessions — under
DD_ANONYMOUS_AUTH_CONFIRM=true, both the system and container log-stream upgrades now pass the same auth gate as every REST endpoint (#636). - Explicit 501 for missing agent lifecycle transport — start/stop/restart/rollback on an agent-owned container without controller Docker transport returns a 501 naming the cause instead of an ambiguous 404 (#637).
v1.6.0-rc.11 Highlights — August 1, 2026
- Portwing 0.9 Docker watcher, updates, and lifecycle actions now run controller-side — Drydock 1.6.0-rc.11+ recognizes the exact
transport=docker-api,execution=controller,events=portwingwatcher marker, starts its native registry checks and Docker action against a loopback-only authenticated bridge, and carries Docker calls over Standard HTTP or Edge correlated request/response/stream transport. Single/batch updates, start/stop/restart, update preview, and backup rollback use this path even though Portwing advertises no remote trigger. Portwing stays authoritative for lifecycle events, and raw false/unknown inventory no longer erases controller enrichment (#632, #637, Portwing #76). - Standard Portwing request signing matches signature version 2 — Ed25519 agents now receive five headers including
X-Portwing-Signature-Version: 2; the signature covers the exact escaped request path plus its unmodified raw query instead of a decoded path with the query omitted.
v1.6.0-rc.10 Highlights — July 31, 2026
- Infrastructure-mode self-updates no longer fail to spawn the helper container on Docker Hub installs — for containers labeled
dd.update.mode=infrastructure, the helper image reference is now resolved through the registry provider'sgetImageFullNamenormalization instead of a raw Hub URL, so it matches the daemon-local image name and the helper actually pulls (#645, fixes #644). - Startup warning for minute-precise maintenance-window crons — the Docker watcher now logs a one-time warning at init when
DD_WATCHER_{name}_MAINTENANCE_WINDOW's minute field doesn't contain*, a common copy-paste trap that opens the window for one minute per hour instead of the whole range; the update confirm dialog also links to the update-eligibility reasons reference (Discussion #639). - Unchanged
update-availableaudit entries are no longer re-recorded on a timer — audit rows are now written only on first detection or when the update target/kind changes, instead of every time the dedupe window lapses (#642). - Maintenance-window documentation corrected — examples now use
* 2-6 * * *instead of a minute-precise cron that was described as an hourly range, and the update confirm dialog's wording is documented verbatim in Update Eligibility & Blockers (#640). - Translations resynced from Crowdin (#620) and routine dependency maintenance across the app and UI workspaces, including the
fast-urisecurity override advancing to 4.1.1 (#614, #615, #617, #646).
v1.6.0-rc.9 Highlights — July 28, 2026
- Portwing edge log streaming is on by default — the authenticated container-log WebSocket now bridges correlated chunk/end/error frames from the edge gateway, cancels the agent stream when the viewer closes, and caps each downstream viewer at 1 MiB of buffered data; older Portwing agents degrade gracefully to their one-shot response.
DD_EXPERIMENTAL_PORTWING=falseremains available as an emergency disable. - The row status overlay is no longer clipped at the pinned-column edge — the "Updating/Queued/Scanning" chip is now layered above the sticky identity-cluster cells instead of being cut in half at the cluster boundary (#631).
- Registry errors no longer replace the container's tag — a rate-limit/auth/not-found registry error used to overwrite or duplicate the tag cell with a danger pill; the tag column now always shows the actual tag, with the registry column glyph remaining the single error flag (#631).
v1.6.0-rc.8 Highlights — July 28, 2026
- Agent-managed containers keep their update policy — remote agents never learn controller-side runtime overrides, so every agent report carried an empty override layer that the controller persisted verbatim, wiping maturity mode, min-age days, skip lists, and snoozes on every sync or recheck. The controller now reapplies its stored overrides when ingesting agent reports, and the store only honors an empty override layer when the update-policy API marks the clear as deliberate — so settings finally survive agent syncs while UI clears still stick (#565).
- Auto-update keeps running when update notifications are scoped to specific channels — assigning any notification trigger to the update-available rule silently disabled every action trigger (Docker, Docker Compose, Command) fleet-wide, because action triggers were run through an allow-list they're structurally barred from joining. Action triggers are now exempt from the allow-list membership check; disabling the rule itself remains the kill switch (#623).
v1.6.0-rc.7 Highlights — July 26, 2026
- Four identity-drift bugs fixed — the maturity soak clock no longer resets when a container is recreated, notification dedup no longer double-fires a
once: truenotification on a manual recheck, a finishing security scan no longer reverts an update the watcher detected mid-scan, and containers with no available update no longer surface under maturity/age filters or sorts. All four traced back to inconsistent candidate-identity comparisons, now unified behind one shared helper. - Icon bundle regenerated —
ui/src/boot/icon-bundle.jsonis back in sync with the locked iconify versions, fixing drift since the tabler 1.2.35 → 1.2.37 lockfile bump.
v1.6.0-rc.6 Highlights — July 26, 2026
- Maturity-cleared updates get their own notification — when an update held back by the maturity gate (
maturityMode: mature) finally clears its soak window, drydock now fires a dedicatedmaturity-clearednotification right away instead of waiting on the next scan's generic update-available message (Discussion #587). A background sweep checks gated containers every few minutes so the notification lands close to the moment the update actually becomes applicable, and it's deduplicated againstupdate-availableso nothing gets announced twice. - Routine dependency and translation maintenance — minor and patch bumps across the app, UI, demo, website, and end-to-end workspaces, plus a refreshed translation sync from Crowdin covering the container, dashboard, list, and shared-component catalogs.
- CI scripts no longer choke on empty results — steps that piped a
greporfindstraight into a variable assignment could abort before their own "nothing found" fallback ever ran, since an empty match exits non-zero underbash -eo pipefail. Fallible commands are now guarded individually so a real failure still fails loudly and an empty result still falls through to its handler.
v1.6.0-rc.5 Highlights — July 23, 2026
- Resources can give the table its space back — the Resources column remains visible by default, but the column picker can now hide it and preserve that choice. Source, release-note, and registry shortcuts move into each row's More menu while hidden; cards keep the same shortcuts in their footer without duplicating them.
- Phone-width Dashboard and Containers stop overflowing — crossing into a single-column breakpoint re-syncs the dashboard widget layout so widgets no longer spill past the viewport, single-column card lists drop the empty band below reflowed cards, and long stack names in the Containers group header truncate instead of pushing the update-all button off-screen (#498).
v1.6.0-rc.4 Highlights — July 22, 2026
- Pinned freshness is honest at a glance — an informational newer-version insight now reads Major, Minor, or Patch instead of contradicting the visible newer tag with “Current.” It remains non-actionable and cannot fire update actions or notifications.
- Same-tag rebuilds are explained — “Image update” replaces “Digest update,” with tooltip copy explaining that the tag now points to a different image build and a redeploy pulls it.
- The Containers layout keeps context — Software Version is explicitly named and folds before Host at constrained laptop widths. Users can also set, change, or clear a presentation-only group from a container's More menu; clearing an override restores Docker-derived grouping.
- Release-gated E2E failures are attributable — Cucumber reuses the exact QA image built by CI, verifies a six-fixture manifest, restores scenario-mutated state, and publishes structured reports and diagnostics without blanket scenario retries. Playwright owns browser rendering and retains first-failure media without whole-test retries.
v1.6.0-rc.3 Highlights — July 21, 2026
- Honest update states across the UI — digest-only changes read "Digest update" instead of a version label, an unrecognized update kind shows a neutral "Unknown" badge, the security bouncer's block state reads "Security hold", and the NEW/MATURE freshness badges are gone in favor of one maturity clock panel that names the clock the gate actually measures against.
- Pinned is a tag property, not an update status — pinned containers carry a persistent pin glyph beside the tag (driven by the real pin-gate verdict, with the
dd.tag.familyremedy in its tooltip), and insight-only pinned rows read "Current" everywhere, including the container-detail Update Status panel. - Security hardening batch — anonymous access fails closed on upgrades (
401API responses,/health503) unless explicitly confirmed, the HTTP notification trigger blocks SSRF against metadata/link-local targets including via redirects, WebSocket upgrades validate the complete origin, the session cookie is namespaced todrydock.sid, store files are owner-only, and icon CDN sources are pinned to exact revisions. Docs: Authentication, HTTP trigger. - Maturity countdown stability — a manual recheck no longer restarts the maturity soak when only display metadata wobbled; the clock resets only when the update candidate's tag or digest actually changes.
- Container-list fit and follow-through fixes — the column picker labels columns auto-hidden to fit (and its "+N" badge tooltip stays fresh), registry deep-links from container detail land on the registry they name, and the removed-API-path banner pluralizes correctly.
v1.6.0-rc.2 Highlights — July 18, 2026
- Watch errors no longer erase update state — a container whose registry check fails keeps its last successful comparison alongside the recorded error instead of appearing never-compared, errored containers reuse the same fast refresh path as healthy ones, and repair rebuilds preserve the registry-reconciled digest that keys security scan grouping.
- "Version Update" container filter — a new filter kind shows only real semver upgrades (major/minor/patch) and hides digest-only churn; bookmarkable via
?filterKind=version. - Docs aligned with shipped behavior — FAQ and README corrections (registry list, scanner parity, runnable
config migratecommands), the removedWS /api/log/streamalias documented in Deprecations, and the quickstart registry/tag matrix refreshed.
v1.6.0-rc.1 Highlights — July 15, 2026
- Notification rules are now a complete control surface — configure delivery routing, audit-backed in-app bell categories, update severity threshold, and per-provider simple/batch templates in one detail panel; preview drafts before saving. Container-unhealthy events are bell-capable, and agent status changes refresh the bell promptly. Docs: Actions & Notifications, Notification rules API.
- The dashboard uses a custom zero-dependency grid — edit-mode mouse/touch reorder, bounded resize, deterministic packing, hidden widgets, responsive layouts, reset, and local or opt-in cross-device persistence replace
grid-layout-plus. Docs: Dashboard customization, UI preference sync. - v1.6 deprecation removals are enforced — only argon2id Basic hashes, HTTPS OIDC discovery,
DD_*environment variables, anddd.*labels are active at runtime. Removed WUD names remain recognized only by the migration CLI. Trigger-taxonomy aliases stay functional for their final warning release but now log aterrorlevel. Docs: Deprecation schedule, Migrating from WUD. - Polling and log-view hot paths are hardened — per-poll repository tag lists are deduplicated, aggregate endpoints avoid full-collection deep clones, large log collections are virtualized, and the 2,000-row live system-log client buffer rolls forward without freezing newest-first views. Docs: Logs.
- Compose updates resolve the active file chain explicitly — configured label, standard Compose project labels, live inspect fallback, then trigger-level
FILE, with multi-file ordering, working-directory resolution, bind-mount mapping, and optional mount-prefix matching. Docs: Docker Compose auto-detection.
Documentation Refresh — v1.5.2
- Podman and Docker socket security docs refreshed — The watcher docs now cover Podman's Docker-compatible API path (#152), direct socket vs proxy/TCP caveats, rootful/rootless socket paths, SELinux troubleshooting, remote Docker over TLS, and OIDC remote watcher auth. This is documentation and compatibility guidance. Docs: Podman Quick Start, Docker Socket Security, Podman FAQ.
- Config and API reference parity — The current docs were reconciled against runtime config schemas, Express routes, and the OpenAPI spec. Configuration tables now match the implemented env vars and defaults more closely, while the API docs cover canonical
/api/v1routes, authentication component endpoints, registry webhook signature auth, notification outbox actions, bulk container updates, backup listing, and auth status aliases. Docs: Configuration, REST API, OpenAPI spec.
Release Highlights — v1.5.1-rc.4 (June 29, 2026)
- Container Version and Uptime fields —
image.softwareVersionnow powers detail panels and the new Version column, while DockerState.StartedAtpowers detail uptime and the optional Uptime column. Docs: Watcher container runtime details, Tag and Version columns. - Release notes expanded — The release-notes popover includes current, available, and intermediate semver releases. Trigger templates can reference
${currentReleaseNotes}alongside${releaseNotes}. Docs: Release notes popover, Trigger template variables, Container release-notes API. - Docker Compose mount-prefix fallback —
DD_ACTION_DOCKERCOMPOSE_{name}_MOUNTPREFIXFALLBACK=truecan match compose paths by trailing<project-dir>/<file>when bind mounts expose a different prefix inside Drydock. Docs: Docker Compose trigger — Mount-prefix fallback. - Maintenance and maturity gates tightened — Auto-apply now respects maintenance windows on every detection path, Docker Hub/GHCR publish dates can clear mature updates immediately, and a changed pending tag/digest restarts the soak clock. Docs: Update eligibility blockers, Maturity filter configuration.
- Upgrade note for v1.5.1+ stores — Newer
dd.jsonfields are tolerated on later downgrades, but rolling back from v1.5.1 to v1.5.0 still requires removingdetails.startedAtandimage.softwareVersion. Docs: Storage. - One-time mature-update burst — On the first poll after upgrade, Docker Hub or GHCR updates that are already older than
maturityMinAgeDaysmay immediately fire notifications or action triggers. Docs: Maturity policy gate.
Release Highlights — v1.5.0 (June 22, 2026)
- Default watcher cron changed to every 6 hours — The default
DD_WATCHER_{name}_CRONvalue changed from0 * * * *(hourly) to0 */6 * * *(every 6 hours) to stay within anonymous Docker Hub and GHCR rate limits on larger inventories. Deployments that setDD_WATCHER_{name}_CRONexplicitly are unaffected. To keep near-real-time detection, setDD_WATCHER_LOCAL_CRON=0 * * * *. Docs: Watchers configuration. - 17 UI locales — The UI now ships with English plus 16 non-English locales: Simplified Chinese, Traditional Chinese, Italian, Spanish, German, French, Brazilian Portuguese, Dutch, Polish, Turkish, Japanese, Korean, Russian, Vietnamese, Ukrainian, and Arabic. Switch language in Config > Appearance. Docs: UI Customization — Language.
DD_LOG_FORMATdefault is nowtext— The official Docker image defaults to human-readable log output. SetDD_LOG_FORMAT=jsonfor structured output consumed by log aggregators. Docs: Logs, FAQ.- Notification outbox — Failed notification deliveries are persisted and retried with exponential backoff. A new Notification outbox page (under Settings) shows pending, delivered, and dead-letter entries with retry and discard actions. Docs: Triggers.
- Diagnostic debug dump — One-click export from Config > Diagnostics collects a redacted snapshot of recent events.
GET /api/v1/debug/dump?minutes=30. Docs: Configuration > Diagnostics, API.
Release Highlights (March 2026)
- Shared data table sizing — Tables now share one sizing model with stable columns, sticky actions, pointer/keyboard resize, double-click autosize, and persisted per-view widths. Containers also folds lower-priority metadata at narrow widths instead of compressing primary identifiers. Docs: UI customization — Data Tables.
- System log live streaming — The Configuration Logs view now supports live WebSocket system logs with level/component filtering and tail backfill.
Docs: Logs configuration, Log API stream endpoint. - Watcher run-time visibility — Watchers expose
metadata.lastRunAtandmetadata.nextRunAt, and the UI surfaces these in the Watchers and Agents views as Last run and Next run.
Docs: Watcher API. - Container list query enhancements — Container list now supports
order, runtimestatusfilters (running,paused, etc.), and watched-statekindfilters (watched,unwatched,all).
Docs: Container API query parameters. - Digest notification mode — Triggers can accumulate updates and flush on schedule with
MODE=digest+DIGESTCRON.
Docs: Trigger modes. - Signed registry webhooks — Added HMAC-verified registry push webhook endpoint for targeted checks.
Docs: Webhooks. - Podman API compatibility improvements — Watchers now handle Podman API-version differences more reliably (including redirect-prone unversioned API paths).
Docs: Watchers Podman setup, Podman FAQ. - Container-action failure toasts — UI now surfaces update/delete failures as visible toast notifications instead of silent failures.
Docs: Container actions. - Unified update-completion toasts (v1.5.0-rc.19) — Update, rollback, and cancel result toasts now appear the moment the operation completes, and are replayed correctly after a reconnect.
Per-container Update Policy (February 9, 2026)
This update adds skip/snooze controls per container to reduce noisy repeated notifications for known-bad versions.
What changed
- Added container-level update policy in store:
skipTagsskipDigestssnoozeUntil
- Added API endpoint:
PATCH /api/v1/containers/:id/update-policy- Actions:
skip-current,clear-skips,snooze,unsnooze,clear
- Added UI controls on container cards:
- Skip current update
- Snooze for 1/7/30 days
- Clear snooze or all policy
Behavior
updateKindstill reflects the detected remote update.updateAvailableis suppressed while policy applies (skip/snooze), and becomes true again when a new unmatched version/digest is detected or snooze expires.
Trigger Coordination Improvements (February 9, 2026)
This update improves how triggers can be coordinated when they share the same trigger name (for example docker.update and discord.update).
What changed
1. Trigger execution ordering
You can now control trigger execution order with:
DD_ACTION_{trigger_type}_{trigger_name}_ORDER / DD_NOTIFICATION_{trigger_type}_{trigger_name}_ORDER
- Lower values run first
- Default is
100 - If two triggers have the same
ORDER, they are sorted by trigger id
Example:
DD_ACTION_DOCKER_UPDATE_ORDER=10
DD_NOTIFICATION_DISCORD_UPDATE_ORDER=20This ensures the Docker update trigger runs before the Discord notification trigger for the same update event.
2. Trigger name aliases in container labels
Container labels dd.action.include / dd.notification.include (and their .exclude counterparts) now accept either:
- full trigger id (
docker.update) - trigger name alias (
update)
Example:
dd.action.exclude=updateThis applies to every action trigger named update (for example docker.update and dockercompose.update).
Each label filters only its own trigger category. dd.action.include / dd.action.exclude never filter notification triggers, and dd.notification.include / dd.notification.exclude never filter action triggers. To exclude both docker.update and discord.update, set both labels:
dd.action.exclude=update
dd.notification.exclude=updateThe legacy labels dd.trigger.include / dd.trigger.exclude were removed in v1.7.0 and no longer resolve to anything; only the scoped dd.action.* / dd.notification.* labels are read.
3. Shared threshold by trigger name
Triggers sharing the same trigger name can share THRESHOLD automatically:
- if exactly one threshold value is explicitly set among same-name triggers, that value is inherited by the others
- if multiple different threshold values are set, no inheritance is applied
Example:
DD_ACTION_DOCKER_UPDATE_THRESHOLD=minorWith no explicit Discord threshold, discord.update inherits minor.
Recommended setup for "update then notify"
DD_ACTION_DOCKER_UPDATE_THRESHOLD=minor
DD_ACTION_DOCKER_UPDATE_ORDER=10
DD_ACTION_DOCKER_UPDATE_PRUNE=true
DD_NOTIFICATION_DISCORD_UPDATE_ORDER=20
DD_NOTIFICATION_DISCORD_UPDATE_URL=<discord_webhook_url>
DD_NOTIFICATION_DISCORD_UPDATE_SIMPLETITLE=Updated ${container.name}
DD_NOTIFICATION_DISCORD_UPDATE_SIMPLEBODY=Container ${container.name} has been updated from ${container.updateKind.localValue} to ${container.updateKind.remoteValue}