v1.5.0-rc.29
Full Changelog: v1.5.0-rc.28...v1.5.0-rc.29
[1.5.0-rc.29] — 2026-05-31
Added
-
Trigger environment variable taxonomy split —
DD_ACTION_*andDD_NOTIFICATION_*prefixes. Action triggers (Docker, Docker Compose, Command) are now configured withDD_ACTION_*anddd.action.*labels; notification/messaging triggers (Slack, SMTP, Discord, Telegram, ntfy, Pushover, and all others) are configured withDD_NOTIFICATION_*anddd.notification.*labels. All three prefix families (DD_ACTION_*,DD_NOTIFICATION_*,DD_TRIGGER_*) are interchangeable at runtime — merge priority isDD_NOTIFICATION_*>DD_ACTION_*>DD_TRIGGER_*. A migration CLI (drydock config migrate --source trigger) rewritesDD_TRIGGER_*,dd.trigger.include, anddd.trigger.excludeto action-prefixed aliases automatically; use--dry-runto preview changes before applying. -
Up-to-date and pinned badges in Kind column — Containers table now shows a green check-circle badge ("Up to date") for containers at their latest version, and a green pin badge ("Pinned") for containers with skipped updates, replacing the previous dash placeholder.
-
Real-time container log viewer — WebSocket-based live log streaming from Docker containers directly in the UI. Features ANSI color rendering, automatic JSON log detection with syntax-highlighted pretty-printing, free-text and regex search with match navigation, stdout/stderr stream filtering, log level filtering for structured logs, copy to clipboard, and gzip-compressed download. Available in both the container detail panel and a dedicated full-page view at
/containers/:id/logs. (Phase 4.2) -
Diagnostic debug dump — One-click export of redacted system state from Configuration > Diagnostics. Collects runtime metadata, component state (watchers, registries, triggers, agents), Docker API diagnostics, MQTT Home Assistant sensors, recent Docker events, store stats, and
DD_*environment variables. Sensitive values matchingpassword|token|secret|key|hashare automatically redacted. Configurable time window (1–1440 minutes). (Phase 4.14) -
Container log streaming API —
WS /api/v1/containers/:id/logs/streamendpoint with Docker binary stream demultiplexing, session-based authentication on WebSocket upgrade, and fixed-window rate limiting (1,000 connections per 15 minutes). -
Container log download API —
GET /api/v1/containers/:id/logsendpoint with gzip compression support, stdout/stderr filtering, configurable tail size, and timestamp-basedsincefiltering. -
Debug dump API —
GET /api/v1/debug/dumpendpoint with configurableminutesquery parameter for time-windowed event collection. -
Dashboard customization — Customizable grid layout with drag-to-reorder, resize, and per-widget visibility toggles using
grid-layout-plus. Edit mode via pencil icon in breadcrumb header. Customize panel with checkboxes and S/M/L size badges. All widgets progressively collapse content based on container height. -
Resource usage dashboard widget — CPU and memory usage bars with top-N resource consumers, progressive detail at different widget sizes.
-
Fleet-aggregate stats subsystem (commits
feature/v1.5-rc17). NewContainerStatsAggregatorpolls each locally-monitored container once per tick (default 10 s) and computes a fleet-wideContainerStatsSummary(total CPU%, total memory, top-N rows). Two new endpoints —GET /api/v1/stats/summaryandGET /api/v1/stats/summary/stream— expose the current snapshot and a live SSE feed; the dashboard Resource Usage widget now consumes the SSE stream directly, fixing the regression (introduced in rc.13 by the?touch=falseworkaround) where the widget showed zeros because the per-container cache was never warmed. The legacyGET /api/v1/containers/statsendpoint and the client-sidesummarizeContainerResourceUsagerollup have been removed. -
Per-container update locks (commit
761fb834). New keyedLockManagerprimitive inapp/updates/lock-primitives.tsreplaces the module-levelpLimit(1)that was serialising every container update across the entire process. Lock keys are derived per container (and per compose project forDockercompose), so two unrelated containers can now pull and recreate concurrently while two services in the same compose project still serialise correctly. -
Restart recovery for queued and pulling updates (commit
00788b13). Startup reconciliation inapp/store/update-operation.tsis now selective:status=queuedoperations stay queued for the recovery dispatcher to pick up, andphase=pullingrows are reset toqueued(pull is idempotent). A newapp/updates/recovery.tsmodule runs once afterregistry.init(), re-resolves trigger and container for each queued operation, and dispatches them through the existing fire-and-forget pipeline. -
Notification outbox with retry and dead-letter queue (commits
a9561d93,7d2ef6eb,b215d295,ce26bece). NewnotificationOutboxLokiJS collection andapp/notifications/outbox-worker.tsbackground worker provide durable retry semantics for notification dispatch. On failure, the delivery intent is persisted to the outbox and the worker retries on a periodic drain with exponential backoff + jitter. After a configurable number of failed attempts (default 5) entries transition to the dead-letter queue; delivered and dead-letter entries are auto-purged past TTL (default 30 days). New/api/notifications/outboxREST surface lets operators list entries, retry from the DLQ, or discard. -
Notification outbox UI (commit
feature/v1.5-rc17). NewNotification outboxpage (route/notifications/outbox, nav under Settings) with status tabs (Dead-letter / Pending / Delivered), retry and discard actions. -
Cancel queued or in-flight updates (commits
4b79e3ac,79487115).POST /api/operations/:id/cancelnow accepts both queued and in-progress operations. Queued ops are marked failed immediately; in-progress ops are flagged via acancelRequestedfield and the lifecycle observes the flag at three safe checkpoints. -
Global concurrent-update cap (
DD_UPDATE_MAX_CONCURRENT). New counting semaphore provides a configurable global gate on how many update lifecycles run simultaneously. Default0= unlimited. Positive integerNmeans at most N updates run concurrently. Self-update operations bypass the global cap. -
Health-gate SSE heartbeat (
DD_UPDATE_HEALTH_GATE_HEARTBEAT_MS). While drydock waits for a new container to pass its health gate, a periodic heartbeat re-emitsphase: 'health-gate'at a configurable interval (default 10 s).DD_UPDATE_HEALTH_GATE_HEARTBEAT_MS=0disables heartbeats; values below 1000 ms or non-integers fail fast at startup. -
Self-update now works when Drydock reaches the Docker daemon over a TCP host, not only through a bind-mounted
/var/run/docker.sock(commitfc34ffb9).resolveHelperDockerConnectionnow inspects the watcher's Dockerode connection: a TCP host produces a TCP helper attached to Drydock's own Docker network. The bind-mounted-socket path is unchanged. -
The per-container Update button is locked with a
Self-update unavailableindicator when Drydock cannot update itself in the current deployment (commitcf777280). A new hardself-update-unavailableupdate-eligibility blocker is raised when self-update cannot run over either a bind-mounted socket or a TCP host. -
i18n coverage extended to the remaining hardcoded UI strings across 28 components (discussion #329). All 16 non-English locales now have full key parity with the English source. 17 locales ship in the picker: de, es, fr, it, nl, pl, pt-BR, tr, zh-CN, zh-TW, ar, ja, ko, ru, uk, vi, plus English.
-
DD_AGENT_ALLOW_INSECURE_SECRETescape hatch for closed-LAN deployments. rc.20 tightened the agent-secret-over-HTTP check to a hard error. rc.21 introducesDD_AGENT_ALLOW_INSECURE_SECRET=trueas an explicit controller-side opt-in for environments where the operator accepts that the agent secret travels in cleartext. Default behavior is unchanged. -
Security scan digest mode. Every scan cycle now carries a stable
cycleId(UUID v7) and emits asecurity-scan-cycle-completeevent. Triggers can configureSECURITYMODE=digest(orbatch+digest) to receive one summary per cycle. Templates are customizable viaSECURITYDIGESTTITLE/SECURITYDIGESTBODY. (#300) -
Opt-in scheduled-scan notifications — New
DD_SECURITY_SCAN_NOTIFICATIONS=trueflag enablessecurity-alertevent emission from scheduled scans. Default isfalse; on-demand scans always emit. -
Bulk security scan endpoint —
POST /api/v1/containers/scan-allscans all (or a filtered subset of) watched containers server-side, streams per-container progress over the existing scan SSE channel, and honors client-disconnect aborts. Rate-limited to 1 request / 60s per IP (authenticated-admin bypass). -
SSE Last-Event-ID replay (#289) — The server stamps every broadcast event with a monotonic
<bootId>:<counter>id and retains a 5-minute time-bounded ring buffer. Clients reconnecting with aLast-Event-IDheader receive every event they missed; if the buffer has evicted the requested id the client receives add:resync-requiredevent. -
Update-eligibility blockers on container rows — Backend surfaces 12 structured blocker reasons per container, rendered inline on the Containers list so users see why a row isn't updating without opening the detail drawer.
-
GET /update-operations/:idendpoint — Returns the current state of a specific update operation for reconciliation when the terminal SSE is missed. -
Inline update action in Security view (#299) — Image rows in the Security view now show an "Update" action button directly next to the vulnerability data when a newer image is available.
-
Watcher next-run metadata (#288) — Watcher API and Agents view now show when each watcher will next poll for updates, with an absolute-timestamp tooltip on hover.
-
Backend-driven update queue — Container updates are now queued server-side with per-trigger concurrency limits. UI shows Queued → Updating → Updated state progression with sequence labels (e.g. "Updating 1 of 3").
-
Registry 429 / 503 retry with Retry-After and per-host token bucket (commit
ffd1b57b, #342). A newwithRetryhelper wraps every registry HTTP call: on 429 or 503 it honors the upstreamRetry-Afterheader, then falls back to exponential backoff (1 s / 2 s / 4 s, capped at 60 s), up to 3 retries. A new per-host token bucket prevents the watcher from self-inflicting rate limits during a large cron cycle. -
Release notes inline popover (commit
09475fa6). The release-notes icon on container rows now opens an inline popover showing both the current and the available-version release notes side by side, with expand/collapse per panel. -
Container source project shortcut link (Discussion #295) — Containers now render a clickable "View project" link next to release notes when an
org.opencontainers.image.sourceOCI label,dd.source.repooverride label, or GHCR-derived source URL is available. -
Actionable deprecation banners (Discussion #214) — The 5 deprecation warning banners now show the concrete migration action inline and include a "View migration guide" link that deep-jumps to the relevant anchored section of the deprecations docs page.
-
Notification dropdown rework + themeable zebra stripes (Discussion #267) — Header carries the "Notifications" title plus a "Clear" text button, each row shows a per-entry dismiss affordance, and a split footer exposes "Mark all as read" + "Open audit log". Introduces
--dd-zebra-stripe, a new theme token. -
Notification history store — New LokiJS collection (
notifications_history) records a per-(trigger, container, event-kind) result hash soonce=truededup survives process restarts. -
Floating tag detection and UI indicator — New
tagPrecisionclassifier (specific|floating) detects mutable version aliases and auto-enables digest watching on non-Docker Hub registries. Container detail views show a caution badge when a floating tag is detected without digest watching enabled. (Discussion #178) -
Hide Pinned containers toggle — Checkbox in the container list filter bar hides containers pinned to specific versions. Persisted in user preferences. (Discussion #250)
-
Combined batch+digest notification mode — Triggers can now use
MODE=batch+digestto send both immediate batch emails and scheduled digest summaries. (#254) -
Multi-select event-type filter in audit log (commit
5e2d0c70, Discussion #332). The audit log's event-type filter is now a checkbox dropdown supporting any combination of event categories simultaneously. -
Bearer token auth for
/metricsendpoint — SetDD_SERVER_METRICS_TOKENto authenticate Prometheus scrapers viaAuthorization: Bearer <token>. -
Disable default local watcher — Set
DD_LOCAL_WATCHER=falseto prevent the built-in Docker watcher from starting, useful for controller-only nodes that manage remote agents exclusively. -
Multi-server notification identification (#283) — Notifications automatically include a
[server-name]prefix when agents are registered. Controller name configurable viaDD_SERVER_NAME. Custom templates can usecontainer.notificationServerNameandcontainer.notificationAgentPrefix. -
Infrastructure update mode —
dd.update.mode=infrastructurelabel for socket proxy containers enables helper-swap update path bypassing the socket proxy. -
i18n framework migration (refs #329). Bulk vue-i18n migration into per-namespace JSON catalogs under
ui/src/locales/en/(eight namespaces auto-loaded byimport.meta.globinboot/i18n.ts). Foundation for the Crowdin integration. 17 locales ship in the picker. -
Design system components — Added shared UI building blocks:
AppIconButton,AppBadge,StatusDot,DetailField, andAppTabBar. (Discussion #199) -
Podman API version negotiation — Docker watcher probes the daemon's
/versionendpoint over the Unix socket and pins Dockerode to the reported API version. PreventsEAI_AGAINcrashes caused bydocker-modem's redirect-following bug when Podman returns HTTP 301 for unversioned API paths. (#182) -
System log live streaming in UI — Added end-to-end WebSocket support for system logs (
/api/v1/log/stream) with new UI service/composable and live log view integration. -
System log viewer overhaul — Toolbar stays pinned at top, long lines wrap at viewport width, search matches component/level/channel fields, filter toggle shows only matching entries, sort toggle switches between oldest-first and newest-first. (#259, #260)
-
Rollback shortcut in container actions menu — Quick rollback option directly from the container row actions dropdown.
-
SPA + hashed-asset cache-control — Static UI assets with hashed filenames are served with immutable long-lived cache headers; the SPA
index.htmlcarries a short revalidation header.
Changed
-
Default watcher cron relaxed from hourly to every 6 hours (#342 follow-up).
app/watchers/providers/docker/Docker.tsnow defaultscronto0 */6 * * *(every 6 hours) instead of0 * * * *(hourly). Users who setDD_WATCHER_{name}_CRONexplicitly are unaffected. Users who want near-real-time detection can still setDD_WATCHER_{name}_CRON=0 * * * *. -
Action trigger default mode — Action triggers (
docker,dockercompose,command) now default toAUTO=onincludeinstead ofAUTO=all, requiring an explicitdd.action.includelabel before auto-updating containers. (#213) -
Self-update helper now prefers the bind-mounted Docker socket over a TCP watcher connection (commit
aa828d88). The resolution order is now inverted:findDockerSocketBindruns first, and if the target container carries a socket bind the helper uses that direct socket path regardless of the watcher's TCP configuration. TCP is the fallback for pure socket-less deployments. -
DD_SESSION_SECRETauto-generated and persisted when unset. On first boot withoutDD_SESSION_SECRETset, drydock generates 64 random bytes and writes them to asecretscollection inside/store/dd.json. Subsequent boots read the persisted value so sessions survive restarts. The env var still takes precedence when set. (rc.21 restored this after rc.20 made it a hard requirement without a migration path; existing deployments that set the variable see no change.) -
Watcher dispatch is fully fire-and-forget (commit
5cfa2286).Trigger.runUpdateAvailableSimpleTriggerandrunAcceptedUpdateBatchno longer awaitrunAcceptedContainerUpdates, so a slow update lifecycle no longer stalls the next watcher tick. -
"Update started" toasts renamed to "Update queued" (commit
79487115). Dispatch is fire-and-forget — the text now matches what actually happened. -
Shared DataTable column sizing overhaul (commit
596adcd2). All first-party table surfaces now route through the sharedDataTablecomponent with numeric sizing metadata, supporting pointer and keyboard column resizing, double-click autosize, and persistent manual/autosized widths per table. -
Crowdin export configuration aligned with app locale folders.
-
Tag-last release pipeline (fixes #306). Collapsed
release-cut.ymlandrelease-from-tag.ymlinto a single workflow where the git tag push is the last step, performed only after the Docker image has been built, pushed, signed with cosign, attested (SLSA), and the signed release tarball has been verified. Enforces the invariant: if the git tag exists, the image exists. -
Playwright E2E tests moved to a dedicated workflow file (
e2e-playwright.yml) (commitf0989301). OSSF Scorecard's CI-Tests check now scores independently from the main ci-verify suite. -
Translations refreshed from Crowdin (commit
202f3d83). Human translations synced from Crowdin for the rc.23 i18n extraction sweep, updating the 16 non-English locales across all UI namespaces. -
Security alert emit is non-blocking inside the update lifecycle (commit
6c5198dd).SecurityGate.maybeEmitHighSeverityAlertnow returns synchronously after firing the emit; the lifecycle no longer waits for sequential provider notifications. -
Expand all / Collapse all bulk toggle — Replaced the single chevron toggle in the Containers toolbar with an explicit "Expand all" / "Collapse all" button.
-
Soft eligibility blockers de-emphasized by default (Discussion #325). Soft blockers now render with neutral muted styling so hard blockers visually dominate the row, and active blockers sort hard-first.
-
Responsive dashboard layout persistence — Dashboard widget bounds and layout are now breakpoint-aware, persisting separate layouts per viewport tier.
-
Trigger digest flush DRY refactor —
flushDigestBuffer/shouldHandleDigestContainerReportare now parameterized on the event kind so update-digest and security-digest share a single implementation. -
Healthcheck execution path optimized — Default HEALTHCHECK probe replaced with a 65KB static C binary (
/bin/healthcheck). curl is retained for backward compatibility with user-defined HEALTHCHECK overrides during the deprecation window (scheduled for removal in v1.7.0, final warning release v1.6.0). -
Agent reconnect notification — New opt-in
agent-reconnectnotification rule that fires when a remote agent reconnects after losing connection. Disabled by default. -
app/updates/locks.tsrenamed toapp/updates/lock-primitives.ts(commit4c506d21). The module now contains general-purpose synchronisation primitives (Semaphore,LockManager) not tied to the updates subsystem. -
Exact-version package.json pinning — Flipped the four remaining caret specifiers to exact versions so every package.json matches the already-exact lockfile resolutions. Every dependency layer is now SHA-immutable.
-
Dashboard reconnect refresh is live-only. On
dd:sse-connectedthe dashboard now refetches only endpoints that can go stale between frames;dd:sse-resync-requiredstill forces a full fan-out.
Deprecated
-
DD_TRIGGER_*environment variable prefix anddd.trigger.*container labels (deprecated v1.5.0, removal targeted v1.7.0). UseDD_ACTION_*/dd.action.*for update-action triggers (Docker, Docker Compose, Command) andDD_NOTIFICATION_*/dd.notification.*for messaging/notification triggers (Slack, SMTP, Discord, Telegram, ntfy, Pushover, and all others). The legacy prefixes continue to work as aliases through v1.7.0. A migration CLI (drydock config migrate --source trigger) rewrites existing configs automatically. SeeDEPRECATIONS.mdfor the full schedule. -
dd.action.include/dd.action.exclude(and legacydd.trigger.include/dd.trigger.exclude) become hard manual-update blockers in v1.7.0. v1.5.x keeps them as soft blockers — the pill reads Trigger filtered / Trigger excluded but the manual Update button stays clickable (with a warn-and-confirm). v1.7.0 will lock the button and reject the API call when the labels filter out the matching trigger. See DEPRECATIONS.md for migration guidance. -
curlhealthcheck override —curlis retained in the image for user-defined HEALTHCHECK overrides during the v1.5.x deprecation window; removal is scheduled for v1.7.0 (v1.6.0 is the final warning release).
Removed
-
Experimental
eligibilityPills.{showSoft,deemphasizeSoft}preferences dropped before release. The de-emphasis is now baseline behavior with no toggle. -
Experimental
containers.showAutoUpdateDiagnosticpreference +compactvariant ofUpdateEligibilityBadgesdropped before release. -
Two pre-existing unused imports flagged by biome's
noUnusedImports(updates/request-update.tsdefaultTriggerimport;Docker.containers.processing-retrieval.test.tsmockGetFullReleaseNotesForContainerimport). -
Legacy
GET /api/v1/containers/statsendpoint andsummarizeContainerResourceUsageclient-side rollup removed; superseded by the new fleet-aggregate stats subsystem (GET /api/v1/stats/summary).
Fixed
-
#391 — A failed Docker Compose update no longer destroys the running container.
refreshComposeServiceWithDockerApipreviously removed the old container before recreating it, so any failure of the create step (e.g. the newly-pulled image is not available for the host platform) left the service with zero containers and no recovery. The update path now (1) performs a pre-flight check that the pulled image is architecture-compatible with the host and aborts before removing the old container if it is not, and (2) wraps the stop/remove → recreate sequence so that, if recreate still fails, the original container is restored from its captured spec and the original error is re-thrown. The running container is never lost on a failed update. -
#402 —
/healthnow returns503until passport authentication strategies are fully registered, closing a startup race where the port accepted connections before auth was ready (intermittent 401s on startup).registerRoutes()in the auth subsystem now callssetAuthReadyFnbeforeauth.init(), wiring the readiness gate before strategy registration begins. Deployments that hit intermittent 401 errors during the first seconds after container start should see the issue resolved. -
#386 — A fresh-restart agent whose in-memory store has not yet been re-populated no longer wipes the controller's last-known container state on handshake.
AgentClient._doHandshakenow skipspruneOldContainerswhenevercontainers.length === 0(andhasConnectedOnceis true) and emits a warning. Pruning is deferred to the next authoritativedd:watcher-snapshot, which is already gated on!containerEnumerationFailed && enrichmentErrors === 0and is therefore unambiguous. -
#386 — Agents intermittently showing 0 running containers in the controller UI (multiple recurrences across rc.20–rc.26). The complete fix spans three layers: (1) rc.20 introduced
containerEnumerationFailedinDocker.watch()to suppress snapshots whengetContainers()throws; (2) rc.25 extended suppression to per-container enrichment errors via adiagnostics.enrichmentErrorsout-parameter; (3) rc.26 added a dedicatedAgentStatsChangedevent so the controller UI also refreshes on Docker-event-driven container changes (starts/stops between cron cycles), not only completed cron cycles. -
#289 — Agent-hosted container updates no longer leave an orphaned queued operation row on the controller that the 30-minute TTL sweep force-fails into a misleading "update failed" notification. The fix threads the controller's
operationIdend-to-end:AgentTrigger.trigger/triggerBatchnow accept and forwardruntimeContext;AgentClient.runRemoteTriggerextracts per-container operationIds and includes them in the agent payload; the agent-side controllerrunTriggeraccepts and threads theoperationIdintorequestContainerUpdate; a newAgentClient.resolveAgentOperationIdhelper reuses the controller-side row when found. The controller-side queued row therefore transitions directly toin-progressandsucceeded/failedfrom the agent's lifecycle events, eliminating the spurious "update failed" notification. -
#289 — Update-applied and update-failed notification triggers and UI success toasts no longer silently drop for containers running on a connected agent. The agent's container snapshot is now threaded through
buildAgentOperationBase,ensureAgentOperationForTerminal,markAgentOperationTerminal, and related helpers, stampingagent: this.name. The store's terminal-lifecycle emit therefore naturally carries the agent's container intoemitContainerUpdateApplied/emitContainerUpdateFailed, and both the notification trigger and the SSE toast fire end-to-end on the controller for agent-originated updates. -
#289 — Container rows no longer drop sort position during an in-flight update, and every terminal outcome (succeeded / failed / rolled-back) now fires a toast. The operation display hold captures a sort-field snapshot at hold start so the row stays pinned through the docker recreate window.
-
#290 — "Updated Successfully" toast no longer drops intermittently after a container update. A new
useGlobalUpdateToastcomposable mounted once atApp.vueis the single source of truth: listens fordd:sse-update-applied/dd:sse-update-failed/dd:sse-batch-update-completed, survives route navigation, dedupes byoperationIdover a 5-minute window, and waits for the matchingdd:sse-container-added/updated/removedevent before firing. -
#291 — Dashboard update flow now fires the same toast sequence as the Containers view and shares the same
useOperationDisplayHoldcomposable, fixing the last reporter symptoms (updating row no longer drops to the bottom mid-update; dropped terminal SSE no longer leaves the dashboard silent). -
#342 — A container is no longer shown as "update available" with a blank target version after a transient registry error.
hasRawUpdateinapp/model/container.tsnow performs the tag comparison only when bothimage.tag.valueandresult.tagare defined, matching the existing guard ingetRawTagUpdate. -
#342 — GitHub release-notes lookups now survive GitHub's secondary rate limit instead of giving up on the first burst.
GithubProviderclassifies a403as a secondary rate limit only when it carries aretry-afterheader orx-ratelimit-remaining: 0, retries those, and arms a short module-level cooldown driven by GitHub's own retry hint. ThewithRetryhelper gains optionalretryPredicateandretryDelayMshooks. -
#342 — Registry routing always uses the credentialed instance when one is configured (commit
069274fe). The router now gives explicit priority to credentialed instances. The silent anonymous fallback on 401/403 is also removed; auth rejection now throws an actionable error that surfaces as the "Check failed" badge in the UI. -
#342 — The registry-error tooltip on the Containers view now names the registry that failed.
registryErrorTooltipnow derives the registry hostname from the container'sregistryUrland renders it through a new i18n string, e.g.ghcr.io — Request failed with status code 429. -
#342 — Hybrid
image:tag@sha256:digestrefs no longer trigger a spurious "Cannot get a reliable tag" warning when Docker'sRepoTagsis empty.Docker.resolveImageNamenow detects hybrid refs and parses them directly viaparse-docker-image-name; only true digest-only refs fall through to the existingresolveDigestOnlyImagepath. -
#342 — Containers list "Version" column shows the correct data for all container types. Digest-pinned containers continue to show the
sha256:abc… → sha256:def…pair; floating-tag + digest-watch containers render the human-readable tag with the digest delta in the tooltip; hybrid digest containers show the tag with the digest pair inline. -
#355 —
update-failednotifications no longer drop silently when the controller's container store races against post-failure prune.UpdateLifecycleExecutornow carries the failing container on theupdate-failedpayload, andTrigger.handleContainerUpdateFailedEventacceptspayload.containeras the primary source with the store lookup as fallback. -
#357 — Transient Trivy failures no longer wipe previously-stored scan history. The scheduler now keeps the existing record when the new result is an error and the existing record is less than 7 days old. Error results are also no longer indefinitely re-spawning fresh Trivy invocations —
scanImageWithDedupuses a 15-minute error retry floor. -
#357 / #355 — Trivy scan and SBOM no longer require
/var/run/docker.sockinside the drydock container. The forced--image-src dockerflag is removed; Trivy now uses its default source order and falls back to a registry pull when the local daemon isn't reachable. SetDD_SECURITY_TRIVY_IMAGE_SRC=remoteto skip the local-daemon probe. -
#370 — Containers list "Version" column again shows the human-readable image tag for floating-tag + digest-watch containers (rc.20 inadvertently reverted the #356 fix). The
updateKind === 'digest' && !isDigestPinnedbranch has been restored to the correct behavior:CopyableTagwith the full digest delta in the cell tooltip. -
#371 — Containers "Group By Stack" view no longer dissolves a multi-container stack into "Ungrouped" while its last container is mid-update. A new
groupAssignedSizeMapref records each group's API-assigned member count; the flatten condition now requires bothbuckets[key].length === 1andgroupAssignedSizeMap.value[key] === 1. -
#374 — Security scans no longer hand Trivy a raw registry v2 API URL. The
resolveContainerImageFullNamefallback now strips the URL scheme and the/v2path segment, matchingRegistry.getImageFullName, yielding a plainregistry-1.docker.io/image:tagreference. -
#385 — Telegram, Pushover, and other notification triggers no longer silently swallow
update-appliedandupdate-failedevents after a compose recreate or on multi-agent deployments. The fix persists a snapshot of theContaineron the operation entry at enqueue time (createAcceptedContainerUpdateRequest) andbuildTerminalLifecycleEventBasenow forwards that snapshot on the terminal-lifecycle payload. The agent SSE wire was also extended to forward the container snapshot end-to-end. -
#328 — Triggering a security scan emitted a "container update" notification instead of the security digest.
renderBatchTitle/renderBatchBody/composeBatchMessagenow honourruntimeContext.title/.bodyverbatim when set, so the security-digest path produces the configuredsecuritydigesttitle/securitydigestbodyoutput instead of update-available output. -
#317 — A notification trigger configured with
auto: falseno longer also silently loses lifecycle notifications (update-applied,update-failed,security-alert,agent-connected,agent-disconnected). Auto-fire-on-detection handlers stay gated byauto; lifecycle handlers register unconditionally. -
#317 — Update button bypassed eligibility blockers, queuing requests the API would only reject one-by-one. The API now rejects manual updates on any hard blocker with the blocker's user-friendly message; the UI locks the Update button on hard blockers and shows a confirm-modal warning on soft blockers.
-
#315 — Self-update now works against private registries whose
registry.urlis stored as the v2 API base (e.g.https://ghcr.io/v2).resolveHelperImagenow normalizes the reference to matchRegistry.getImageFullName. -
#308 — Per-row scanning chip is now correctly anchored to the container being scanned and no longer floats in viewport-fixed gutter space. The scan lifecycle uses a
useScanLifecyclecomposable maintaining ascansInFlightset keyed by container id (with a 120s safety timeout). A siblingdd-row-scanningclass provides the containing block without dimming the row. -
#305 — Hide Pinned now hides every pinned container again, matching rc.8 behavior. (#293 had carved out an exception for pinned rows with a pending update.)
-
#301 —
GET /api/containerspreloads all active update operations in a single indexed scan, replacing the rc.8 per-container 3-scan fan-out. Synthetic per-attach time drops from ~5–25 ms to ~0.02–0.04 ms on large inventories. -
#296 — Controller identity detection now runs for host-based watchers (TCP to a local socket-proxy, the common Synology / Docker Compose pattern). Set
DD_SERVER_NAMEto override. -
#293 — Hide Pinned filter no longer hides pinned containers that have a pending update. Filter decluttering is preserved for static pinned containers; rows with a pending update (
newTagtruthy) surface through the filter. -
#282 —
batch+digestmode now sends both the immediate batch email and the scheduled morning digest for each detected update, matching the documented semantics. The fix splits the digest channel off as its ownNotificationEventKind('update-available-digest') so batch-channel and digest-channel dedup are independent. -
#270 — Hide-pinned filter now uses computed
tagPinnedproperty instead of stale stored field. Unconditional startup repair ensures tagPrecision data is always correct. -
#256 — Update operation state disambiguated by container ID instead of name, preventing cross-host bleed between same-name containers on different hosts.
-
#253 — Shorthand trigger references resolved in notification rule matching; notification buffering keys stabilized; debug logging added to every silent filter path.
-
#248 — API guard against duplicate container updates (409 conflict).
-
#245 — Container update fails with 500 error when no healthcheck — Health gate now skipped when
dd.rollback.autois not set. Pre-healthy timeout usesmax(120s, dd.rollback.window)instead of a fixed value. -
#238 — Container inspect Config.Image fallback — When Docker summary only exposes a
sha256:…image ID (no RepoTags), container discovery falls back to container inspectConfig.Imageto recover the original tagged reference. -
#229 / #228 — Spurious SMTP emails after update —
clearDetectedUpdateState()now clears rawresult/updateKinddata instead of the derivedupdateAvailableboolean. -
#223 — Dashboard layout customizations lost on page reload — Added
gridLayouttoPreferencesSchema; reorder now usesloadPersistedLayout. -
#222 — Dashboard customize panel responsive on mobile — Panel is opt-in on mobile (sliders icon to open), full-screen overlay with backdrop dismiss.
-
#217 — Dashboard Resource Usage widget minimum height raised from 3 grid units to 7 grid units so per-container CPU and Memory lists stay visible.
-
#213 — Dashboard Host Status widget no longer auto-scrolls to the last host when the host list changes. The scroll-snap classes, dynamic tail-spacer element, and measurement machinery have all been removed.
-
#208 — Dashboard updates widget 6-item cap removed — Removed hard-coded
RECENT_UPDATES_LIMITthat silently dropped entries beyond 6 in the Updates Available widget. -
#202 — CalVer zero-padded month in strict family filter — Tags like
2026.02.0were rejected when the current tag was2025.11.1because zero-padded single digits were treated as a family mismatch. -
#200 — Dashboard widget mobile scroll — Added
overscroll-containto all scrollable dashboard widgets. -
#192 — Digest-only image visibility — Watchers no longer silently drop containers with digest-only image references.
-
#186 — Registry failures in Updates Available widget — Containers with "check failed" status no longer appear in the dashboard "Updates Available" section.
-
#183 — Cascading -old container updates — "Update All" batch no longer triggers updates on containers renamed with
-old-{timestamp}suffix during a prior update. -
#182 — Podman pod infra containers skipped — Watchers now skip Podman pod infrastructure containers that have an empty
Imagefield. -
#180 — Duplicate containers after recreate — Three-layer deduplication filtering prevents alias containers from entering the store during Docker recreate cycles.
-
#156 — Container alias name canonicalization —
getContainerName()now strips Docker recreate alias prefixes (e.g.8bf70beac570_termix→termix) before the name enters the store, so all triggers receive canonical names. MQTT Home Assistant sensor preserved during recreate (replacementExpectedflag prevents premature empty retained discovery payload). -
Discussion #295 — Release-notes icon in the container table now always opens the same popover, even when only an external release URL is available. The popover now renders uniformly for both cases.
-
discussion #295 —
DD_SESSION_SECRETno longer crashes startup when unset — The fallback is now a persisted secret: on first boot withoutDD_SESSION_SECRETset, drydock generates 64 random bytes and writes them to the store. Subsequent boots read the persisted value. (See also theChangedentry above.) -
#362 — SSE reconnect exponential backoff no longer collapses to a flat 1 s loop when the agent is struggling. The backoff now only resets after the stream has stayed open for
SSE_STABLE_CONNECTION_MS(30 s). -
AgentClienttimers are now cleared when an agent is removed, preventing orphaned timeouts (commit03bf7211). A new idempotentstop()method cancels bothstableConnectionTimerandreconnectTimer. -
#368 — OIDC custom-dispatcher paths (cafile /
DD_AUTH_OIDC_*_INSECURE=true) no longer fail with an opaqueTypeError: fetch failedon Node 24. The fix importsfetchfromundiciand uses it whenever a custom dispatcher is required so both halves share the same dispatcher version. -
OIDC warn logs now surface the full
error.causechain, making TLS and DNS failures actionable (commit720d99a3). -
OIDC error logs now redact RFC-1918 IP addresses and absolute filesystem paths (commit
9b79de77). -
OIDC SSO broken after upgrade/restart — OIDC discovery made lazy so startup failure no longer drops the provider. Redirect, callback, and token paths retry on first use. (#246)
-
Image reference construction — unanchored
/v2strip could silently corrupt references when the image name contained a/v2path segment. The fix extracts a shared pure helperbuildImageReference(app/registries/image-reference.ts) that cleans the registry URL before concatenation using anchored regexes. -
ECR stale auth token cache write avoided on concurrent key change. The cache write is now keyed on the credentials snapshot captured at request start.
-
Icon proxy serves fallback image on upstream CDN timeout or 5xx. Non-existence failures now route through the existing fallback path and serve the placeholder image.
-
SBOM endpoint returns
503instead of500when the security scanner is disabled. -
Malformed
dd.tag.transformregex patterns — The regex-transform label validator now throws at config time for malformed or oversized patterns. -
dd.registry.lookup.imagelabel no longer corrupts deploy identity (commit594a07e8, fixes #336).normalizeContainerno longer overwritesimage.name/image.registry.url; a newgetImageForRegistryQueryhelper applies the substitution only at each query boundary. -
Password-manager autofill restored on login form (commit
3abe2fa6, fixes #335). Username and password inputs now carrynameandidattributes. -
security-scan-skippedaudit row now fires when the gate is disabled globally (commitae24e0a9). -
Command action security warning updated to canonical
DD_ACTION_COMMAND_*prefix (commitaa5fc98d). -
Docker event history pruning amortized to reduce per-event splice cost (commit
d6690cc8). The threshold is now2×maxEntries, so splices are amortized across many appends. -
Agent container list no longer shares mutable LokiJS references (commit
1f7d8034). The handler now clones each container viacloneContainerbefore stripping metadata. -
Docker multi-arch build no longer fails when Alpine repos drift between archs. The curl entry is now unpinned so apk installs whatever's current per-arch.
-
Telegram MarkdownV2 escaping in all trigger paths — Body text in
trigger()andtriggerBatch()was not escaped for MarkdownV2 reserved characters, causing Telegram API 400 errors and silent notification failures. (Discussion #211) -
#310 — Restored the
[server]/[agent]prefix on default notification body templates that rc.10 had stripped. -
#309 — Status column in the Containers list now shows its label alongside the icon at typical widths.
-
#296 — Notification server-name prefix no longer renders the container ID on Docker Compose setups.
-
#283 — Duplicate server name in notification prefix and suffix suppressed.
-
#271 — Log sort order persists across navigation.
-
#265 — Stale update detection suppressed from pre-clear watcher scans.
-
#323 — Popovers on the Containers list rendered off-screen for the last row + drifted on scroll — Added a
buildPopoverStylehelper that measures available space and flips the popover upward when needed; added a global scroll listener that closes both popovers. -
Watcher "Last Run" display — Watchers page now shows relative timestamps for last run. (#189)
-
#187 — Agent column picker positioning fixed.
-
#184 — Dashboard confirm dialog —
ConfirmDialogmoved to global app shell so update prompts from the dashboard appear immediately. -
Stack/group view no longer collapses to ungrouped mid-update when containers are recreated.
loadGroups()now indexes the map under id, name, AND displayName. -
#340 — Self-update no longer preserves stale Drydock version metadata. The self-update clone path now drops image-inherited environment variables and labels from the old image when the target image changed them.
-
#345 — Host names with numeric suffixes no longer lose the differentiating character in the Containers table.
-
Truncated release notes body now marked with trailing ellipsis (commit
3a9bd098). -
Accepted update dispatch failures now logged (commit
674a0ed8). -
Row update overlays anchored to first data cell width (commit
4bdb8d65). -
Update state lost on navigation/refetch — Backend list endpoint now enriches containers with in-progress update operation state.
-
Digest-only image visibility — Watchers no longer silently drop containers with digest-only image references. Digest watch now stays enabled when Docker summary exposes a
sha256:…image but container inspect recovers a tagged reference. -
Same-name container update holds isolated to the correct instance (commit
02433a02). Added anidentityKeydiscriminator so the hold follows the container's stable identity through id changes. -
Security view container chooser traps keyboard focus (commit
e98603c1). Added standard focus-trap: focus is moved to the first focusable element on open, Tab/Shift+Tab cycle within the popover, Escape returns focus to the previously-focused element. -
Legacy
xlink:hrefSVG attributes stripped by icon sanitizer (commit0309bacb).hrefis now in the allowlist; the deprecatedxlink:hrefform remains blocked. -
Floating semver aliases excluded from greater-than check (commit
0b9eaaf3).isGreaterCandidateTagnow requires strictly greater semver in one direction and not-greater in the reverse, so floating aliases like3.3and3.3.0drop out of the candidate set entirely.
Security
-
TCP Docker host is validated before the self-update controller passes it to Dockerode (commit
441b4358).validateTcpDockerHostrejects values that contain a URL scheme prefix, a userinfo segment, whitespace, or path separators, throwing a descriptive error before any network connection is attempted. -
Proxied SVG icons sanitized before caching (commit
54d93a3b). SVG payloads fetched from upstream icon CDNs are run through an allowlist-based sanitizer before being written to the icon cache. -
Command action trigger env values sanitized to strip shell metacharacters (commit
1113d8ca). Container-derived values injected into the command subprocess environment are now stripped of shell metacharacters ($,`,;,(,)) before the environment map is passed toexecFile. -
Credential redaction expanded to
x-registry-auth,*-token, andapi-keyfields (commit4417ce25). A second regex pass now redactsx-registry-auth, any field matching*-token, andapi-key/api_keyvalues before the payload leaves the server. -
Credential status pattern matching uses RE2 (commit
df9b914a).BaseRegistry.getRejectedCredentialStatusnow usesRE2JS.compile(…)to maintain the project-wide ReDoS immunity guarantee. -
Registry instances using
insecure=truenow log a warning on every request (commitcd14e3a9). -
DD_SESSION_SECRETis now required; auto-generated and persisted on first boot when unset. (SeeChangedentry above for the full history of rc.20 vs rc.21.) -
Agent connections over plain HTTP with a configured secret are now rejected at startup (commit
7c6f6c20). Use HTTPS or a TLS-terminating proxy for agent connections that require a shared secret. -
GHCR token fallback treats whitespace-only tokens as missing (commit
711d583c). All token checks now call.trim().length > 0. -
GHSA-xq3m-2v4x-88gg (critical) — Bumped
protobufjs7.5.4 → 7.5.5 to close the prototype-chain arbitrary-code-execution advisory. -
GHSA-r4q5-vmmm-2653 (medium) — Bumped
follow-redirectsto 1.16.0 to close the custom-auth-header cross-domain leak advisory. -
Hook command grammar validator — User-supplied pre-update / post-update hook commands are now validated against a restricted shell-safe grammar at config time.
-
OIDC authorization endpoint strict match — The OIDC flow now requires an exact match against the discovered authorization endpoint.
-
OIDC token redaction in error logs — Error log lines from the OIDC pipeline redact bearer / id / refresh tokens.
-
Rate-limit key derivation — Unauthenticated rate-limit buckets now key on
socket.remoteAddressin preference torequest.ip, eliminating theX-Forwarded-Forspoof-ability. -
CORS origin required when enabled — Enabling CORS now requires
DD_SERVER_CORS_ORIGINto be set explicitly. -
OIDC redirect target allowlist — Post-login redirects are now validated against the backend's endpoint allowlist.
-
Healthcheck HTTPS probe hardening —
/bin/healthcheckno longer usespopen()with shell command interpolation. The probe now locates the openssl binary explicitly, fork/execs it with pipes, and uses poll-driven I/O with SIGPIPE handling. -
SSE log IP hashing with opt-in raw mode — SSE connect/disconnect lines log a salted hash of the source IP (
h:xxxxxxxx) by default. SetDD_SSE_DEBUG_LOG_IP=trueto temporarily log raw IPs. -
HTTP trigger proxy URL scheme validation — HTTP trigger proxy URLs must now be
http://orhttps://schemes. -
Vulnerability CSV export escape hardening — Every CSV field is now quoted unconditionally, and tab/CR leading characters are escaped alongside
=+-@. -
Vite CVE patches — Updated vite to 8.0.7 (ui) and 7.3.2 (demo) to fix CVE-2026-39363, CVE-2026-39364, CVE-2026-39365.
-
Axios CVE-2025-62718 — Updated axios 1.13.6 → 1.15.0.
-
fast-xml-parser override 5.5.8 → 5.7.1 — Addresses GHSA-gh4j-gqv2-49f6 / CVE-2026-41650.
-
uuid 13.0.0 → 14.0.0 — Addresses GHSA-w5hq-g745-h8pq.
-
fast-xml-parserupgraded to 5.5.8 — Addresses CVE-2026-33349 (numeric entity expansion bypass). -
Log injection prevention — Removed version string interpolation from startup and migration log messages.
-
Reflected XSS in Podman redirect guard — 404 handler no longer reflects request URL in response body.
-
WebSocket origin and lockout hardening — Added stricter WebSocket origin validation and safer lockout file-permission handling.
-
Agent log entry sanitization — Agent log proxy endpoint now uses an allowlist-based normalizer that only forwards known fields.
-
Security bouncer enforcement on container updates — Update and Update All actions now enforce the security bouncer gate.
-
Vulnerability URL and CSV sanitization — Vulnerability URLs validated before rendering; CSV export fields sanitized against formula injection.
-
Snyk policy file — Added a repo-level
.snykfile for reviewed false-positive Snyk Code findings. -
Supply-chain toolchain refresh — Bumped pinned Alpine edge/testing package versions for
cosignandtrivyin the Dockerfile. -
Binary indices and drain concurrency cap for notification outbox (commit
9393253e).findReadyForDeliveryfields switched to binary indices for O(log n) B-tree lookups.OutboxWorkergains amaxDrainConcurrencyoption (default 10) backed by aDrainSemaphore.
Dependencies
-
Vite 7.3 upgraded to 8.0 — Migrated to Vite 8.0 with Rolldown bundler.
-
Patch/minor dependency bumps — Updated all patch/minor dependencies and upgraded knip to v6.
-
Vulnerable transitive dependency patches — nodemailer 8.0.3→8.0.4, picomatch→4.0.4, brace-expansion→5.0.5, smol-toml→1.6.1, yaml→2.8.3, next 16.2.1→16.2.2 (CVE-2025-59472), lodash 4.17.23→4.18.1 (CVE-2026-2950, CVE-2026-4800).
Documentation
-
v1.5.0 deprecation sweep. Migrated every documentation example and test fixture off the v1.5.0-deprecated
DD_TRIGGER_*/dd.trigger.*prefixes onto canonicalDD_NOTIFICATION_*+dd.notification.*(messaging providers) andDD_ACTION_*+dd.action.*(update executors). Touched 29 files incontent/docs/current/**, the in-repo README roadmap, CONTRIBUTING, and all QA/CI/demo compose fixtures. -
Guide/API endpoint synchronization — Updated current docs and guides to consistently use canonical
/api/v1/*paths, expanded container list API docs, and added dashboard customization guide. -
#342 follow-up — Registry env-var naming convention now explained in the registries index. A new "Naming registry instances" callout explains that the
{REGISTRY_NAME}placeholder is a user-chosen label that namespaces multiple instances of the same registry type. -
#342 follow-up — Watcher cron callout explains rate-limit interaction with hourly polling.