Releases: CodesWhat/drydock
Releases · CodesWhat/drydock
Release list
v1.7.0-rc.2
v1.7.0-rc.2
Full Changelog: v1.7.0-rc.1...v1.7.0-rc.2
[1.7.0-rc.2] — 2026-08-20
Added
- Action-policy resolution is surfaced on the API and UI (spec-6.0.1-action-policy.md, slice 5).
GET /api/v1/containersand SSE container payloads now include anactionPolicyobject (state:blocked/manual/auto, plustriggerIdand, when blocked,reason) onupdateEligibility, reflecting the winning action trigger's resolved verdict for that container independent of whether it produced a blocker.GET /api/v1/containers/{id}/triggersgains a per-triggerresolvedStatefield with the same three values, so every candidate action trigger's individual verdict is visible, not just the winner. The container list/detail UI shows a new Auto badge (Update Status panel, full-page Actions tab, side panel trigger rows) wherever a container's or trigger's resolved state isauto, with tooltips explaining the blocked/manual/auto states. Newdd.action.autocontainer label andonautoAUTOmode: underAUTO=onauto,dd.action.includekeeps granting manual access but automatic dispatch additionally requires add.action.automatch, letting a container opt into manual-only access without picking up automatic execution. Drydock now logs a startupWARNfor any action trigger left onAUTO=onincludethat hasdd.action.include-matching containers with no correspondingdd.action.autolabel (would silently drop to manual-only if switched toonauto), and a secondWARNwhen a trigger is onAUTO=nonebut a container carries an inertdd.action.autolabel for it. Docs: newdd.action.autolabel andonautovalue are documented on the labels reference, triggers configuration, and update-eligibility pages, including a callout on all three clarifying thatAUTO=none/AUTO=allalso set the baseline access default (not just automatic execution) —AUTO=nonestill opens manual access to every container,AUTO=allopens both. - Greptile second-opinion review, summoned behind a label. A new thin caller workflow,
.github/workflows/greptile.yml, fires only onpull_request: labeledevents for thesecond-opinionlabel and calls the org's reusableCodesWhat/.githubgreptile-summon.ymlworkflow (pinned to a frozen commit SHA, no floating ref) to post one@greptileaireview request per exact PR head. CodeRabbit stays the automatic review lane; Greptile is opt-in only, for security-sensitive diffs, large refactors, or a tiebreaker when CodeRabbit and the author disagree..coderabbit.yamlgains alabeling_instructionsentry so CodeRabbit can auto-apply thesecond-opinionlabel itself when those criteria hit, in addition to a human applying it manually. (#751) release-cut.ymlcan now cut a maintenance patch from a retired dev branch without touching the cosign signing identity. A new optionalsource_refinput (e.g.dev/v1.6) builds and releases from that branch's tip instead ofmainHEAD, for a patch on a linemainhas already moved past (v1.6.1aftermainis on v1.7). The workflow itself still only ever dispatches fromrefs/heads/main, so the published cosign OIDC identity is unaffected.source_refis bounded hard — it must be an exactdev/vX.Ybranch that exists on origin, sharerelease_tag's line, not be the active (highest) dev line, and pair with a nonzero patch version — and the main-sync drift guard is skipped rather than failed, since there's no drift claim to make against a line main has already superseded. Left empty, every normal cut is unchanged. SeeRELEASING.md's new "Maintenance cuts" section for the full precondition list, including the manualci-verify.yml/e2e-playwright.ymldispatch required before cutting.
Changed
- Documented the container SBOM attestation in the release-verification guide.
release-cut.ymlhas generated and attested a signed SPDX 2.3 SBOM for every release container image since the v1.5 hardening batch, butcontent/docs/current/guides/verifying-releasesnever told readers how to check it. Added a "Verify the container SBOM attestation" section with thegh attestation verify --predicate-type https://spdx.dev/Document/v2.3command the workflow itself uses, and a note on the human-readabledrydock-${TAG}.image.spdx.jsoncopy attached to each release. No workflow change — the SBOM and build-provenance attestations, and their in-workflow verification, already met the house SLSA Build L2 / SBOM-attestation standard; only the user-facing documentation was missing. (#759) - Dropped the trivy qlty plugin in favor of Grype. Grype already owns the vuln-scanning surface (
.github/workflows/security-grype.yml); trivy's qlty-pluginconfigdriver only ever ran its DS*/KSV* Dockerfile misconfiguration checks, which checkov already covers (thecheckov:skip=CKV_DOCKER_3comment onDockerfileline 1, with matching rationale in.trivyignore.yamlfor the standalonetrivybinary the image ships at runtime — a separate concern, untouched here). Removed the[[plugin]] name = "trivy"block and thetrivy:DS002/trivy:DS-0002triage entry from.qlty/qlty.toml; trufflehog stays as-is. (#753) - Faster initial load on the login screen. The app's logo asset had drifted 4x oversized and was still being base64-inlined into the parse-blocking entry chunk (~640KB, reachable before authentication). It's now correctly sized and served as a separately cacheable file, cutting the entry chunk's gzipped size roughly in half and no longer re-downloading on every release. (#805)
- BREAKING: the Home Assistant MQTT topic layout now includes an
agent/<name>segment by default for agent-owned containers. Announced in v1.5.x and scheduled for v1.7.0. Multi-agent deployments previously collided on shared watcher names; the corrected layout scopes topics, sensor counts, and discovery cleanup per agent. Home Assistant references to the old topic paths must be re-pointed. SetDD_NOTIFICATION_MQTT_<name>_HASS_AGENTTOPICSEGMENT=falseto keep the old layout temporarily. Drydock does not retroactively clean up the old, pre-upgrade discovery entities. They remain retained on the broker and show up in Home Assistant as orphaned, frozen-at-last-state duplicates until manually removed.
Fixed
- Dependency-group admission no longer loses restart context when an upstream member is rejected. A restart-only dependent whose upstream was rejected during admission was previously misclassified as a normal update in wave 0 instead of being restarted after its dependency finished (or skipped if it never ran). The original admission batch is now carried through dispatch and API annotation so rejected members no longer corrupt the graph for their accepted dependents. (#718)
- Docker Compose updates no longer carry forward stale image-inherited environment defaults. A compose refresh could leave an old image's default environment value in place after updating to a new image with a different default, even though explicit runtime overrides on the container were always respected correctly. (#736, closes #734)
- Stashed update-policy overrides now survive drydock's own self-update. The
updatePolicyRetentionCachethat lets a recreated container inherit its predecessor's policy overrides lived only in process memory and was silently lost on the exact restart caused by a self-update. It now persists write-through to the store and rehydrates at startup, mirroring the same fix already applied to the maturity-clock cache. (#743, fixes #565)
Fixed
- Drydock no longer reports "Up to date" when an update check failed. A registry error while verifying a candidate's digest discarded the newer tag that had already been found, and the UI presented the result as a confirmed "no update available." A check that could not complete now surfaces as an explicitly unknown status instead of a negative answer. This also covers transient registry failures reported separately. (#814, #808)
- A single malformed container no longer zeroes out an entire agent inventory sync.
AgentClient.processAuthoritativeContainers()— the loop driving both the standard-mode handshake (_doHandshake(), every initial load and reconnect) and edge-modehandleContainerSync()(everydd:container_syncframe) — had no per-container error isolation, unlike the sibling SSE watcher-snapshot path. One container that threw while building its report (for example, during controller-side SBOM document offload) aborted the whole batch before any container reached the store, so a single bad container could make an otherwise-healthy fleet's inventory sync silently report zero containers. Each container in the batch is now processed independently, matching the existing isolation pattern inhandleWatcherSnapshotEvent(): a failure is logged with the container id and the batch continues with the rest. A batch where every container fails now also logs a single proportionate warning, since a silently empty result was what let this go unnoticed for a release cycle. Found while investigating #802; this closes the isolation gap but does not confirm the SBOM path as that issue's trigger — Portwing does not currently sendsecurity.sbomon its container wire format, so #802 remains open pending its actual root cause.
...
v1.6.1-rc.1
v1.6.1-rc.1
Full Changelog: v1.7.0-rc.2...v1.6.1-rc.1
[1.6.1-rc.1] — 2026-08-21
Fixed
- Drydock no longer reports "Up to date" when an update check failed. A registry error while verifying a candidate's digest discarded the newer tag that had already been found, and the UI presented the result as a confirmed "no update available." A check that could not complete now surfaces as an explicitly unknown status instead of a negative answer. This also covers transient registry failures reported separately. (#814, #808)
- Nested OCI image indexes now resolve to the real image manifest. An image whose per-platform entry is itself an index, which is what Buildx produces when SBOM or provenance attestations are enabled, failed with
Unexpected error; no manifest foundand left the container unable to complete a digest check. Drydock now follows the nested index to the platform's actual manifest, bounded to three levels, and correctly ignores the attestation manifest alongside it. (#814) - A single malformed container no longer zeroes out an entire agent inventory sync.
AgentClient.processAuthoritativeContainers()— the loop driving both the standard-mode handshake (_doHandshake(), every initial load and reconnect) and edge-modehandleContainerSync()(everydd:container_syncframe) — had no per-container error isolation, unlike the sibling SSE watcher-snapshot path. One container that threw while building its report (for example, during controller-side SBOM document offload) aborted the whole batch before any container reached the store, so a single bad container could make an otherwise-healthy fleet's inventory sync silently report zero containers. Each container in the batch is now processed independently, matching the existing isolation pattern inhandleWatcherSnapshotEvent(): a failure is logged with the container id and the batch continues with the rest. A batch where every container fails now also logs a single proportionate warning, since a silently empty result was what let this go unnoticed for a release cycle. Found while investigating #802; this closes the isolation gap but does not confirm the SBOM path as that issue's trigger — Portwing does not currently sendsecurity.sbomon its container wire format, so #802 remains open pending its actual root cause.
Note: this is a maintenance cut, built from dev/v1.6 at 9d5b7da896b73861ed1a10fb5c8d0104014af478, not from main.
- The container image and release artifact are cosign-signed (identity
release-cut.yml@refs/heads/main, since the workflow run itself always executes at that ref), but carry no SLSA build-provenance attestation — this workflow's own OIDC token always claimsmainHEAD as the build source, which would be false for this artifact, so attestation is skipped rather than publish a false claim.
v1.7.0-rc.1
v1.7.0-rc.1
Full Changelog: v1.6.0...v1.7.0-rc.1
[1.7.0-rc.1] — 2026-08-14
Added
- Edge Portwing polling cadence is configurable (#688).
DD_PORTWING_POLL_INTERVALsets the controller-owned Edge container-refresh interval in positive integer seconds; the authenticated welcome frame and reported agent metadata use the same value, while absent or invalid values retain the 300-second default. - Installable PWA support (Roadmap Phase 6.9). Drydock is now an installable Progressive Web App via
vite-plugin-pwa: a web app manifest (Drydock, standalone display, theme/background color matched to the One Dark default--dd-bg, 192/512 icons plus dedicated maskable variants with safe-zone padding) and an auto-updating service worker (registerType: 'autoUpdate') that precaches the SPA shell so the dashboard boots offline./api/**is explicitly excluded from all service-worker handling — no navigation fallback, no runtime caching — so a live dashboard never serves stale API data from cache; those requests always hit the network and surface a normal error if it's unreachable. A dismissible install banner (newInstallBannercomponent, following the existingAnnouncementBannerpattern) listens for the browser'sbeforeinstallpromptevent and offers a one-click install, with the dismissal persisted under a versioned localStorage key. iOS home-screen install is supported viaapple-mobile-web-app-capableand the existingapple-touch-icon. The backend's static UI server now servessw.jswithCache-Control: no-cacheso a new deploy is never masked by a browser-cached service worker script. - Clickable port links in the container list and detail views. Each host-published port in a container's
details.portsnow renders as a link (opened in a new tab,rel="noopener noreferrer") instead of inert text — in the side panel, the full-page detail tabs, and new opt-in "Ports" columns/rows in the table and card views. The scheme is auto-detected from the container-side port (443/8443→https://, everything else →http://); the link target host prefers the port's own boundHostIpwhen it's a real address (not0.0.0.0/::/::0), falling back to the agent's configured host for agent-watched containers, or the browser's own hostname otherwise. Internal-only (unpublished) ports still render as plain text. A newdd.port.labelcontainer label lets you attach a friendly name to a specific port (dd.port.label=80=Web UI,443=Admin Console) shown in place of the rawhostPort->containerPort/protocolmapping. - Container uptime, with a live-refreshing display. The existing
details.startedAtfield (from Docker'sState.StartedAt) now also drives an opt-in "Uptime" tooltip showing the exact start timestamp in the container list, and a live "Up …" indicator in the card view's footer — both refresh on a timer and update immediately on SSE container-state changes, matching the full-page detail view's existing uptime display. - Keyboard shortcuts.
/focuses the search bar from anywhere (unless focus is already in a text input),Escapecloses the search bar, and?opens a new shortcut-reference overlay listing the available shortcuts. A/hint now sits next to the existing⌘Khint on the sidebar search button. - Container dependency ordering — data model and detection (v1.7, discussion #219). New
dd.depends_on(comma-separated container names) anddd.depends_on.action(updateorrestart, defaultupdate) container labels. Whendd.depends_onis absent, drydock detects dependencies automatically from a compose-managed container's owndepends_onkey (both short-form arrays and long-form objects; thecondition:key is not yet consulted). A present label always overrides compose detection entirely rather than merging with it. Self-references and unknown compose-service targets are dropped with a logged warning, never a hard error; both fields are re-derived from the container's labels/compose file every watch cycle rather than persisted independently, so they self-heal automatically across container recreation. This lands the data model and detection only — using the resulting graph to order updates/restarts is a separate, later change. - Container dependency ordering — pure graph engine (v1.7, discussion #219). New
app/dependencies/dependency-graph.ts:buildDependencyGraphresolves each container's detecteddependsOnnames against the rest of the fleet (dropping unknown targets and cross-agent edges with a logged warning, never a hard error — cross-host dependency chains remain out of scope for v1.7), andtopologicalSortruns Kahn's algorithm to produce deterministic topological "waves" — arrays of containers safe to dispatch in parallel, tie-broken alphabetically for stable output. A dependency cycle is never a deadlock: cycle members are grouped and scheduled together as one unordered wave, while any non-cycle container downstream of that cycle still resolves correctly in its own later wave. Pure, dependency-free, and unwired — no watcher, trigger, or dispatch behavior changes yet; this only lands the engine that a later change will use to order updates. - Container dependency ordering — execution integration (v1.7, discussion #219). Accepted bulk container updates now dispatch wave-by-wave through
runAcceptedContainerUpdates(app/updates/request-update.ts) instead of all at once, so a container never starts updating before every container it depends on has finished. AdependsOnAction: 'restart'dependent is admitted through the same admission gates as any manual request (widenedupdateAvailablecheck) and, once its dependency finishes updating, is restarted rather than re-pulled via the newrestartDependentContainerprimitive (app/updates/dependency-restart.ts); operations that never got a chance to run because an earlier wave failed land in a newskipped-dependencystatus/phase instead of silently vanishing. The Docker Compose trigger (app/triggers/providers/dockercompose/Dockercompose.ts) reorders multi-servicedocker compose upinvocations by dependency order (sortMappingsByDependencyOrder) so compose itself never fights the same ordering. Maintenance-window batches (app/triggers/providers/Trigger.ts'srunAcceptedUpdateBatch) cascade dependents through the same wave logic once their window opens. - Container dependency ordering — API exposure (v1.7, discussion #219). The container list response gains per-container
dependencyCount/dependentCountbadge counts. New endpoints:GET /api/v1/containers/dependenciesreturns the full resolved dependency graph (nodes, edges, detected cycles, unresolved targets, cross-host-ignored edges);POST /api/v1/containers/:id/update-chain-previewdry-runs the topological waves for the dependency chain rooted at a container without dispatching anything;POST /api/v1/dependency-groups/:rootId/updatebulk-accepts every container in that chain, annotated with the wave index it will actually run in. The preview and dispatch endpoints call the exact samebuildDependencyGraph/topologicalSortpair over the same input set, so the preview can never drift from what an accepted update actually runs. - Container dependency ordering — UI (v1.7, discussion #219). The container list now carries
dependencyCount/dependentCountthrough to the UIContainertype. A new "Update dependency chain" action (confirmDependencyGroupUpdateinuseContainerActions) previews the resolved update waves for a container's dependency chain and shows them in a confirm dialog — including any detected cycle or unresolved-target warnings — before bulk-accepting the chain through the new dependency-groups endpoint. A dedicated dependency-hierarchy grouping view for the container list is deferred to a follow-up. - Debounced container discovery (#156). Docker briefly exposes transient rename aliases while a container is being recreated; drydock previously registered whatever it saw the instant
listContainersreturned it, so a container could momentarily register under its<hex-prefix>_<name>alias. First-seen containers (identified by Docker container ID, not present in the store) now enter a configurable "pending" state and must remain visible for a settling window —DD_WATCHER_{watcher_name}_DISCOVERY_SETTLE_MS, default30000(30s),0disables settling — before they're added to the store, triggers, or the API/UI; pending containers are visible in debug logs only. A deduplicated follow-up watch is scheduled for the earliest pending deadline, so an event-discovered container still registers on time when no further Docker event arrives before the next cron scan. If a pending container is renamed mid-window it registers under the final name once settled; if it disappears mid-window it's silently discarded (debug log only). Containers already known to the store are unaffected and continue to update immediately — settling applies exclusively to first-seen containers, so a same-ID recreation is never blocked from updating for 30 seconds. This complements, and does not replace, the unconditional hex-prefix alias stripping shipped in v1.5 for the same issue (getContainerName/canonicalizeContainerNameinapp/watchers/providers/docker/docker-helpers.ts, and the name-shape-triggered transient-alias suppression infilterRecreatedContainerAliases) — that mechanism is keyed off the container's name looking like a recreate alias, while the new settling wind...
v1.6.0
v1.6.0
Full Changelog: v1.6.0-rc.13...v1.6.0
[1.6.0] — 2026-08-11
Consolidates the 1.6.0-rc.1 … 1.6.0-rc.13 prereleases. Users upgrading from
1.5.2 get everything below; users already on 1.6.0-rc.13 receive no additional
runtime changes.
Added
- Portwing edge and agent integration matures into a first-class transport. Portwing 0.9.0+ agents work over either inbound Standard HTTP or dial-out Edge WebSocket transport, and drydock 1.6.0-rc.11+ recognizes Portwing's own Docker watcher marker to run native registry checks plus single/batch Docker updates controller-side (#632, #637, Portwing #76). Standard-mode agents can sign every request with Ed25519 (
DD_AGENT_{name}_AUTHMODE=ed25519, upgraded mid-series to signature version 2) instead of sending a shared secret. Edge agents stream live container logs and process deletes over the existing WS tunnel with correlated per-request IDs, pick their own display name (bound to their signing key so it can't be squatted), and are proactively disconnected when they stop answering pings. Theportwing/1.0edge endpoint is enabled by default (DD_EXPERIMENTAL_PORTWING=falseremains an emergency disable), backed by a real fleet-soak workflow that runs signed Portwing processes against production infrastructure on every PR and on a weekly schedule. - Declarative, three-tier update policy with a maturity stabilization gate (Discussion #307, Discussion #406, #320). Containers can declare
dd.updatePolicy.*labels (maturity mode/min-age, skip tags, skip digests) with watcher-level defaults and UI/API overrides, resolving through label → watcher default → persistent override precedence and surviving agent refresh, container recreation, and error-state rebuilds. A candidate held back bymaturityMode: matureis visible immediately with a live minute-by-minute countdown to its unlock time, and a newmaturity-clearednotification (backed by a 5-minute background sweep) announces the moment it clears instead of waiting for the next scan. Update Now remains available as an explicit soft-policy override. - Per-rule notification templates, bell preferences, and a new health-status event (Discussion #205, Discussion #198, Discussion #210). The Notifications view can override title/body/batch-title text per rule and provider with a live preview, and independently control which event categories reach the in-app bell — including a new
container-unhealthyrule (disabled by default) that fires off Docker health-check transitions. Bidirectional MQTT lets Home Assistant's Install button trigger a real drydock update through the normal eligibility/dispatch path, rate-limited per container and audited.dd.action.*/dd.notification.*labels are now strictly scoped to their own trigger category instead of one silently gating the other (#494). - Global update mode and an actionable Update Status panel (Discussion #325). Settings → General now selects one server-wide mode —
notify,manual, orauto— and the container side panel and detail view replace the old eligibility-badge stack with plain-language status plus a structured, deep-linking condition list covering all 16 eligibility reasons. Existing installs migrate toautoon upgrade so configured automatic updates keep working; fresh installs default tomanual(see Upgrade Notes). - Cross-device preference sync and a zero-dependency dashboard grid (Discussion #220, #281). An opt-in Sync across devices toggle stores the full UI preference set server-side per user and propagates changes to a user's other signed-in sessions in real time over SSE. The dashboard grid drops its
grid-layout-plusdependency for a deterministic CSS Grid implementation with edit-mode drag/reorder, bounded resizing, touch support, and per-breakpoint layout persistence. Audit, Security, Servers, and Watchers gain the same per-view column show/hide picker Containers and Agents already had. - More signal, less noise in update visibility. A pinned tag now surfaces the best newer same-family tag as a purely informational badge without making it an actionable update candidate (#498); a new Version Update filter isolates real semver bumps from digest-only churn on fleets that rebuild images daily (#538); and a startup warning flags minute-precise maintenance-window crons that only open their window for one minute per matching hour instead of the intended full range (Discussion #639).
- Opt-in wud-card/Homepage compatibility endpoints. With the unversioned
/api/*alias removed (see Removed, below),DD_COMPAT_WUDCARD=true(defaultfalse) mounts a narrow compatibility layer covering exactly the four endpoints the Home Assistant wud-card integration and Homepage'swhatsupdockerwidget call, reshaped into the bare-array response those integrations expect. (Discussion #469)
Changed
- Every major list view is responsive and consistent. Containers, Agents, Notifications, Security, Triggers, Watchers, Servers, Registries, Audit, and Auth now share one
DataTablewith a persisted per-view table⇄card toggle that auto-reflows to cards below ~640px; card views gained a sort control; and Source/Release-notes/Registry resource shortcuts render through one consistent 44px toolbar everywhere they appear (#498, Discussion #295). The Dashboard and Containers views no longer overflow horizontally on narrow phone screens. Update-status vocabulary was overhauled ("Digest update", "Security hold", neutral Major/Minor/Patch badges for informational-only updates), pinned reverted to being a tag property rather than an update status, and the maturity panel now shows exactly one countdown clock instead of two that could disagree with the gate (#556). Table polish across the series fixed pinned-column overlay clipping, a registry error overwriting the visible tag, sticky-column/auto-hide width miscalculation, and a clipped icon column. - Trigger taxonomy migration reaches its final warning stage. Every
DD_TRIGGER_*variable and the deprecateddd.trigger.include/dd.trigger.excludelabels still work in v1.6 but now log aterrorlevel ahead of removal in v1.7; useDD_ACTION_*/DD_NOTIFICATION_*and category-scoped labels, or runconfig migrate --source trigger. - Less duplicate work on the hot paths. Registry tag-list requests are shared across containers within a poll instead of being repeated per container; scheduled security scans and the shared log viewer avoid redundant store writes and re-renders; and transient registry network errors (timeouts, connection resets, DNS blips) now retry with backoff before a watch error is recorded.
- Base image bumped from Alpine 3.21 to 3.24, later rebased onto
node:24-alpineat Node 24.19.0 with a matching Trivy build-stage bump to 0.73.0, closing several HIGH/MEDIUM CVEs in the underlying image (#682).
Deprecated
GET /api/auth/methodsand the legacyGET /auth/strategiesresponse shape are deprecated in v1.6.0 (removal in v1.7.0 and v1.8.0 respectively). Both now log on every request and returnDeprecation/Sunsetheaders pointing callers at canonicalGET /api/v1/auth/status. See DEPRECATIONS.md.
Removed
- Unversioned
/api/*andWS /api/log/streamaliases are gone, returning410 Gone(or a rejected upgrade) instead of serving the request. This is a breaking change for integrations that hardcode the unversioned base path, notably the Home Assistant wud-card integration and Homepage'swhatsupdockerwidget — use theDD_COMPAT_WUDCARDshim above, or migrate to/api/v1/*directly. - Legacy v1.4-era authentication compatibility. Basic auth now accepts only argon2id hashes (
{SHA}, APR1/MD5, crypt, and plain-text hashes fail validation), and OIDC discovery now requireshttps://with no insecure HTTP workaround. - Legacy WUD configuration aliases, obsolete watcher switches, and legacy trigger-template variables.
WUD_*environment variables andwud.*labels are ignored (the migration CLI still recognizes them so existing files can be rewritten);DD_WATCHER_<name>_WATCHDIGEST/WATCHATSTARTare no longer configuration keys (usedd.watch.digest=true; startup watches always run); and$id/$name/$watcher/$kind/$semver/$local/$remote/$link/$countno longer populate in trigger templates. - Kafka
clientIdand token-only public-registry compatibility handling. Kafka validation accepts only lowercaseclientid, and malformed public-registry configurations (a barePUBLIC_TOKENwith noPUBLIC_LOGIN) now fail closed instead of silently falling back to anonymous pulls.
Fixed
- **The maturity soak clock, update-poli...
v1.6.0-rc.13
v1.6.0-rc.13
Full Changelog: v1.6.0-rc.12...v1.6.0-rc.13
[1.6.0-rc.13] — 2026-08-08
Security
- Pinned
js-yamlto 3.15.1 in the e2e workspace (override; transitive dependency) for GHSA-5p4m-2wfm-xmqj (CVE-2026-59870 backport gap: quadratic CPU consumption in!!omapresolution). - Pinned
nanoidto 3.3.18 (override; transitive dependency ofpostcssin the root, app, apps/demo, apps/web, and ui workspaces, and ofartilleryin the e2e workspace) for GHSA-2v37-7h3g-55p8 (CVE-2026-67213) and, in the e2e workspace which was still on 3.3.12, also GHSA-28wg-ghj8-5hjv (CVE-2026-67214). node:24-alpinebase image bumped to Node 24.19.0, replacing the Node 24.18.0 image shipped in rc.12. Picks up Node's July 29 2026 security release, fixing 3 HIGH (CVE-2026-56846, CVE-2026-56848, CVE-2026-58043) + 5 MEDIUM CVEs that landed in 24.18.1 (#682).- Vendored
aquasec/trivybuild-stage pin bumped from 0.72.0 to 0.73.0, resolving 4 HIGH / 6 MEDIUM CVEs in its vendored Go dependencies: go-git (CVE-2026-71556),x/text(CVE-2026-56852), grpc (GHSA-hrxh-6v49-42gf), oras-go (CVE-2026-50151, CVE-2026-50163), and the Go stdlib (CVE-2026-39822) (#682).
Fixed
- Icon bundle no longer silently drops referenced icons at image build time (#683). The bundle is regenerated from the locked
@iconify-jsonpackages during every Docker image build, but the extractor only looked up plain icon entries —lucide:history(the Audit navigation icon in the Lucide icon theme) became an alias in lucide 1.2.121 and vanished from shipped images, rendering blank. The extractor now resolves alias chains, and references that never existed in the locked collections are fixed:iconoir:history→iconoir:clock-rotate-right,iconoir:gitlab→iconoir:gitlab-full,iconoir:stack→iconoir:multiple-pages, and the Font Awesome brand glyphs (GitHub/GitLab/Google/Microsoft registry icons) gained the previously missing@iconify-json/fa6-brandspackage. A new test asserts every icon referenced inicons.tsexists in the committed bundle. - Star History chart is now self-hosted (#671). The homepage card and README embed rendered a broken image after api.star-history.com's global outage (their GitHub tokens rate-limited; starchart.cc also failing). A new
/api/star-historyroute on the website fetches stargazer timestamps from the GitHub API server-side (optionalGITHUB_TOKEN, edge-cached six hours with stale-while-revalidate, short-lived fallback SVG on fetch failure) and renders the chart in the site's own palette for both themes; the README uses a<picture>element with theme-matched variants. No third-party chart service remains in the path, andapi.star-history.comis dropped from the site's CSPimg-src. - Digest-update comparison no longer anchors on an arbitrary
RepoDigests[0]entry (#669). A local Docker image can carry multiplerepo@digestentries for one Image ID (pull/retag accumulation, no ordering guarantee);getRepoDigestblindly took index 0, so a stale or foreign-repo entry landing first anchored the whole digest-update pipeline to the wrong manifest and produced a persistent digest-update false positive that survived applying the update.getOrderedRepoDigests(app/watchers/providers/docker/docker-helpers.ts) now returns every RepoDigests entry whose repo component matches the container's own image reference, ordered, falling back to the full list only when nothing matches; the container model gained an optionalimage.digest.repoDigestsfield carrying that ordered list, re-derived from the live Docker image inspect on every discovery/refresh cycle.handleDigestWatch(app/watchers/providers/docker/image-comparison.ts) now walks that candidate list — a cheap raw-value check first, then a normalize-and-compare registry call per remaining candidate, skipping anchors whose manifest lookup fails — and re-anchorsdigest.repoto whichever candidate actually matched, so a store already poisoned with a staledigest.repoself-heals on its own. A genuine same-tag republish (no candidate matches) still flags an update exactly as before; if every candidate fails to normalize, the failure now propagates instead of being silently coerced into a false "no update".
v1.6.0-rc.12
v1.6.0-rc.12
Full Changelog: v1.6.0-rc.11...v1.6.0-rc.12
[1.6.0-rc.12] — 2026-08-04
Changed
- Routine dependency maintenance across the root tooling, demo, UI, and website workspaces (#653, #654, #655, #656). The dependency-version guard's Next.js check became a 16.x floor instead of an exact pin so routine patch bumps stop tripping it (#664).
- Crowdin translation sync (#665): one French container-component string corrected.
Fixed
- "Agent Mismatch" no longer appears in the container list/SSE display during the brief window an agent's docker/dockercompose trigger is still (re)registering (#605). Eligibility is recomputed live on every read, and
AgentClient._doHandshake()deregisters the agent's components before awaiting the/api/triggersfetch and re-register. A read in that window found zero triggers for the agent andcomputeUpdateEligibilityraised a hardagent-mismatchblocker, disabling the Update button, even though nothing was actually misconfigured — the condition self-corrected once registration finished.agent-mismatchnow downgrades to a soft blocker (button stays enabled) on display surfaces whenever the container's own agent is mid-registration, per the newAgentClient.isRegisteringComponentsflag (true only for the deregister→re-register span, not the whole reconnect backoff). Update admission (app/updates/request-update.ts) is unaffected and stays hard/fail-closed throughout, so an update can never be enqueued through a wrong-agent trigger during that window. - WebSocket log streams no longer reject anonymous-auth sessions (#636). Both WS upgrade paths — the system log stream and the container log stream — gated on
isAuthenticatedSession()requiringsession.passport.user, whichpassport-anonymousnever sets, so underDD_ANONYMOUS_AUTH_CONFIRM=truethe log stream WebSocket always rejected the upgrade even though every REST endpoint worked.isAuthenticatedSessionnow also accepts the session when anonymous authentication is the registered mode. - Maturity clock: swallowed auth errors surfaced, per-container threshold respected (#604).
getImagePublishedAtfailures — including GHCR/LSCR 401/403 auth errors — now log atwarninstead ofdebug, so the maturity gate's silent fallback from the registrypublishedAttoupdateDetectedAtis no longer invisible.getRawUpdateMaturityLevel(app/model/container.ts) andgetContainerMaturityLevel(app/api/container/maturity-filter.ts) now resolve each container's ownupdatePolicy.maturityMinAgeDaysbefore falling back to the globalDD_UI_MATURITY_THRESHOLD_DAYS, matching the gate's ownisUpdateSuppressed/isMaturityGatePendinglogic so the hot/mature badge can no longer disagree with the gate in the same API response. - Container start/stop/restart/rollback return an explicit 501 instead of an ambiguous 404 for agent containers without lifecycle transport (#637).
POST /:id/start|stop|restartandPOST /:id/rollbackreturned a bare 404No docker trigger found for this containerwhenever the lookup missed, indistinguishable from "container not found" — for agent-owned containers this was the only signal the UI got. That lookup miss now returns 501 naming the likely cause (the agent's connection typically hasn't advertisedusesControllerDockerTransport) whencontainer.agentis set; non-agent containers still get the existing 404. This complements the native-transport support that shipped in rc.11 via #651, which closed #637's core gap — this is the remaining explicit-error half.
Security
brace-expansion,ip-address, andfast-urioverrides advanced to patched releases.brace-expansionmoved to 5.0.9 inapp/,ui/, ande2e/(CVE-2026-69152, GHSA-rgw5-rvv9-x895);ip-addressmoved to 10.3.1 inapp/(CVE-2026-54272, CVE-2026-69192, CVE-2026-69198), pulled in transitively viaexpress-rate-limitandmqtt→socks;fast-uriadvanced from 4.1.1 to 4.1.2 inapp/andui/(host confusion via backslash authority introducer, CVE-2026-18446, GHSA-7p8r-x3mc-p8w7, superseding #658).
v1.6.0-rc.11
v1.6.0-rc.11
Full Changelog: v1.6.0-rc.10...v1.6.0-rc.11
[1.6.0-rc.11] — 2026-08-01
Added
- Portwing controller-owned Docker watcher, update, and lifecycle transport (#632, #637, Portwing #76). Drydock 1.6.0-rc.11+ recognizes Portwing 0.9.0's exact Docker watcher marker (
transport=docker-api,execution=controller,events=portwing) and runs its native registry checks plus single/batch Docker updates controller-side. Container start, stop, restart, update preview, and backup rollback actions use that same native Docker capability surface instead of failing because Portwing intentionally advertises no remote trigger. A loopback-only bearer-authenticated bridge carries Docker API calls through Portwing over Standard HTTP or Edge correlatedrequest/response/streammessages, while Portwing remains the lifecycle-event source. Later raw Portwing inventory (updateAvailable=false,updateKind=unknown) preserves rather than erases the controller-enriched update state.
Changed
- Standard Portwing Ed25519 requests now use signature version 2. The controller sends five authentication headers, including
X-Portwing-Signature-Version: 2, and signs the exact origin-form request target (escaped path plus the unmodified raw query) instead of a decoded path without its query.
v1.6.0-rc.10
v1.6.0-rc.10
Full Changelog: v1.6.0-rc.9...v1.6.0-rc.10
[1.6.0-rc.10] — 2026-07-31
Added
- Startup warning for minute-precise maintenance-window crons.
DD_WATCHER_{name}_MAINTENANCE_WINDOWis matched minute-by-minute, so a fixed minute field like0 2-6 * * *only opens the window for one minute per matching hour instead of the whole hour range — a common copy-paste trap reported in Discussion #639. The Docker watcher now logs a one-time warning at init when the configured window's minute field doesn't contain*, pointing at the fix (* 2-3 * * *). Step values like*/5are intentional and are not flagged. - "Learn more" link in the update confirm dialog for policy-blocked overrides. When overriding a soft-blocked update, the confirm dialog now links to the update-eligibility reasons reference, matching the link already shown in the Update Status panel. (Discussion #639)
Changed
- Translations resynced from Crowdin (#620), refreshing the container-component and list-view catalogs across all 15 non-English locales, plus a fuller French pass over the agents, app-shell, common, and config catalogs.
- Routine dependency maintenance across the app and UI workspaces (#614, #615, #617, #646): undici 8.9.0 (app) and the 7.29.0 UI override, express-rate-limit 8.6.1, @aws-sdk/client-ecr 3.1096.0, vue-i18n 11.4.8, knip 6.29.0, postcss 8.5.24, and the
fast-urisecurity override advanced from 3.1.4 to 4.1.1 (both retain the CVE-2026-16221 fix; the dependency-version guard now rejects the still-vulnerable 4.0.0–4.1.0 range). Renovate no longer proposes@babel/coremajors while Stryker's instrumenter requires Babel 7.
Fixed
- Unchanged
update-availableaudit entries are no longer re-recorded on a timer. Previously an unchanged pending update was re-written to the audit log every time the 1-hour dedupe window lapsed, bloating the audit log for fleets with long-pending updates (a fleet with 10 persistent pending updates wrote 140 rows in 24h). Audit rows are now recorded only on first detection or when the update target/kind changes. The RC-onlyDD_AUDIT_UPDATE_AVAILABLE_DEDUPE_MSvariable is removed. - Maintenance-window documentation corrected and made findable (Discussion #639). Every documented example used a minute-precise cron (
0 2-6 * * *) while describing it as an hourly range ("2am–6am daily").MAINTENANCE_WINDOWis matched minute by minute, so that expression opens the window for one minute at the top of each listed hour and reports "closed" the rest of the day. Examples now use* 2-6 * * *, with a table of correct forms in Watchers. The update confirm dialog's own wording ("This update is currently policy-blocked", "Outside maintenance window — auto update deferred until the window opens", "Update anyway") appeared nowhere in the docs, so searching for the on-screen message returned nothing; Update Eligibility & Blockers now documents that dialog verbatim and states plainly that a soft blocker never gates a manual update. - Infrastructure-mode self-updates no longer fail to spawn the helper container on Docker Hub installs (#644). For containers labeled
dd.update.mode=infrastructure, the self-update helper is spawned from Drydock's own image, but the reference was rebuilt asregistry-1.docker.io/codeswhat/drydock:<tag>— the Docker daemon stores Hub images under their short name (codeswhat/drydock:<tag>), the helper image is never pulled since it must already exist locally, and container creation failed with404 No such image, rolling the update back. The helper image reference is now resolved through the registry provider'sgetImageFullNamenormalization (the Hub provider stripsregistry-1.docker.io/,docker.io/,library/) so it matches the daemon-local image name; the previous raw construction remains only as a fallback when no registry manager resolves.
v1.6.0-rc.9
v1.6.0-rc.9
Full Changelog: v1.6.0-rc.8...v1.6.0-rc.9
[1.6.0-rc.9] — 2026-07-28
Added
- Continuous Portwing edge log streams. The authenticated container-log WebSocket now bridges correlated
dd:container_log_chunk,dd:container_log_end, anddd:container_log_errorframes, cancels the agent stream when the viewer closes, preserves stdout/stderr and timestamp decoding, and caps each downstream viewer at 1 MiB of buffered data. Older Portwing agents degrade to their one-shot response without breaking the viewer. - Real Portwing fleet-soak workflow. Pull requests and the weekly quality schedule run actual signed Portwing processes against Drydock's production edge gateway through concurrent exec, sustained logs, forced controller backpressure, and reconnect storms. Machine-readable evidence records RSS/heap budgets and is retained for 90 days.
Changed
- The
portwing/1.0edge endpoint is enabled by default.DD_EXPERIMENTAL_PORTWING=falseremains as an emergency disable for new edge connections. OpenAPI, operator docs, and translated feature summaries now describe the stable/default-on behavior.
Fixed
- The row "Updating/Queued/Scanning" overlay chip is no longer clipped at the pinned-column edge. The table's pinned identity-cluster cells (
sticky z-10) painted over the full-width row overlay hosted in the first cell and cut the centered chip in half at the cluster boundary. While an overlay is active the host cell now sits above its z-10 row siblings (and still below the sticky header). (#631) - Registry errors no longer replace the container's tag in the tag column. A rate-limit/auth/not-found registry error rendered a danger pill instead of the current tag (table mode) or appended one (card mode), duplicating the warning glyph + tooltip the registry column already shows. The tag cell now always shows the actual tag; the registry column glyph remains the single error flag. (#631)
v1.6.0-rc.8
v1.6.0-rc.8
Full Changelog: v1.6.0-rc.7...v1.6.0-rc.8
[1.6.0-rc.8] — 2026-07-28
Fixed
- Auto-update no longer stops when the update-available notification rule is scoped to specific channels (#623). Action triggers (
docker,dockercompose,command) were gated by the same trigger allow-list on theupdate-availablenotification rule that routes messages to notification channels — but action-trigger ids are deliberately barred from that list by the API validator, the UI picker, and the documented rule model, so the moment any notification trigger was assigned to the rule, every action trigger (local and agent-hosted alike) silently failed the membership check withexcluded-from-allow-listand auto-update stopped fleet-wide, with only a debug log as evidence. Action-category triggers are now exempt from the allow-list membership check ingetUpdateAvailableAutoTriggerDispatchDecision(app/triggers/providers/Trigger.ts), mirroring the exemption the lifecycle-notification path has always had; disabling the rule itself still acts as the global kill switch. Present since the rule allow-list landed in v1.6.0-rc.1. - Controller-set update policy overrides no longer vanish from agent-managed containers (#565). Remote agents resolve their own declarative (env/label) policy but never learn controller-side runtime overrides, so every container report they send carries an explicit empty override layer. The controller persisted that layer verbatim, and
updateContainer(app/store/container.ts) treated any presentupdatePolicyOverrideskey as authoritative — clearing maturity mode/min-age days, skip lists, and snoozes on every periodic agent sync or manual recheck. This was the settings-deletion mechanism behind #565, distinct from the soak-clock resets fixed in #568 and rc.7. The controller now reapplies its stored overrides when ingesting agent reports, and the store itself encodes the rule the recreate path has had since #497: an empty incoming override layer carries no controller intent and only clears stored overrides when the update-policy PATCH handler marks the write as authoritative, so deliberate clears from the UI still stick.