Releases: CodesWhat/drydock
v1.5.0-rc.37
v1.5.0-rc.37
Full Changelog: v1.5.0-rc.36...v1.5.0-rc.37
[1.5.0-rc.37] — 2026-06-15
Security
- Patched a batch of newly-disclosed transitive CVEs across every workspace. osv-scanner flagged advisories disclosed 2026-06-15 in build- and test-time dependencies:
vite(CVE-2026-53571, CVE-2026-53632),@babel/core(CVE-2026-49356),form-data(CVE-2026-12143),protobufjs(CVE-2026-54269), andws(CVE-2026-48779). Each is pinned to a fixed version via an override (or a direct bump where the dependency is direct).js-yaml@3.14.2, reachable only through artillery's test-only load-test harness, is triaged as unreachable: its sole fix removes thesafeLoad()API artillery still calls, and it parses only trusted in-repo configs.
Changed
-
Registry rate-limiter burst raised from 5 to 10 for ghcr.io and Docker Hub. The conservative burst allowance was tripping the limiter during legitimate request spikes (enumerating tags across many containers at once); the sustained rate (2 req/s) is unchanged.
-
Hardened the E2E/CI suite against transient flakes. Crash-prone real-application e2e fixtures (Home Assistant, Radarr) now run a keep-alive entrypoint so the watcher consistently discovers the full container set instead of intermittently seeing one short; the test-bootstrap readiness count is now exact and strict; and the Playwright container-detail helpers wait on real conditions rather than fixed timeouts. No shipped runtime behavior changes from this item.
Warning
Upgrade notes — behavioral changes, please read before updating. Releases 1.4.6 and the entire 1.5 line ship security-hardening fixes that change runtime behavior. These are not deprecations: there is no compatibility shim or grace period, so a previously-working deployment can change behavior on upgrade.
- OIDC login now requires
authorization_endpointin your provider's discovery metadata. The authorization-redirect allowlist no longer falls back to a broad same-origin match. Mainstream identity providers (Keycloak, Authentik, Authelia, Okta, Google, Entra/Azure AD, Zitadel, …) publish this field and are unaffected. If your/.well-known/openid-configurationdoes not advertiseauthorization_endpoint, OIDC sign-in will now fail closed — make sure the discovery document exposes it. - Unauthenticated rate-limit buckets now key on the TCP peer address instead of
X-Forwarded-For. Behind a reverse proxy (nginx / Traefik / Caddy), all unauthenticated clients now share a single bucket (the proxy's address), regardless ofDD_SERVER_TRUSTPROXY. Internet-facing or multi-user instances may begin to see unexpected429 Too Many Requestson unauthenticated endpoints. Authenticated requests are keyed per session and are unaffected. - HTTP-trigger
proxyURLs must now use thehttp://orhttps://scheme. Any other scheme (e.g.socks5://) is rejected at config load. Such values were previously accepted but only ever treated as an HTTP proxy — switch to anhttp(s)://proxy URL.
v1.5.0-rc.36
v1.5.0-rc.36
Full Changelog: v1.4.6...v1.5.0-rc.36
[1.5.0-rc.36] — 2026-06-15
Added
-
Experimental Portwing edge-agent mode — agents behind NAT or firewalls can now dial OUT to drydock over a persistent
wss://WebSocket instead of waiting for an inbound controller connection (PR #429, M5). The feature is experimental and opt-in: setDD_EXPERIMENTAL_PORTWING=trueto enable it. When disabled, the endpoint is not mounted and the feature has zero runtime footprint. Once enabled, agents connect toWS /api/v1/portwing/wsusing theportwing/1.0subprotocol. Authentication is Ed25519 public-key challenge-response with timestamp + nonce replay protection (±60 s clock-skew window, 16 MB maximum frame size). Operator key management is exposed through a REST registry at/api/v1/portwing/keys(list, register, revoke). Because the feature is experimental the protocol and API surface may change in a future release without a deprecation notice. -
Remote-agent runtime info now carries
logLevelandpollIntervalin the acknowledgement payload (PR #430, M4). Drydock threads these fields throughbuildRuntimeInfoFromAckand surfaces them in the Agents view alongside the existing runtime metadata.
Fixed
-
Containers pinned to a fully-specified semver tag (e.g.
image:v1.13.3, 3+ numeric segments) no longer climb to newer versions by default (#321). Tags classified asspecificprecision are now treated as digest-only by default —getTagCandidatesreturns an empty tag list so updates track digest changes only, not semver version bumps. Opt in to semver climbing by settingdd.tag.include(restricts climbing to matching tags) ordd.tag.family=loose(unrestricted climbing as before). Floating tags (latest,16-alpine, etc.) and 1–2-segment partial versions are unaffected. -
Maintenance window now gates when auto-updates are applied, not just when update checks run (#321). Previously
watchFromCronrespected the window, butmaybeFastResyncAfterUpdate(the post-update fast resync) calledwatchContainerunconditionally — allowing a triggered resync to detect a new image and dispatch an update outside the window. The fast resync now mirrors the same maintenance-window guard used bywatchFromCron. Additionally,computeUpdateEligibilitygains amaintenanceWindowOpencontext field: whenfalse, a softmaintenance-window-closedblocker is recorded in the eligibility result. Manual UI/API-triggered updates passundefinedand remain ungated. -
Self-update overlay no longer flickers — the UI holds the "Applying Update" screen until the swap is actually complete. Three compounding defects made the self-update experience look broken: (1) the UI's connectivity probe treated any successful
/auth/userresponse as "update finished", but the old server keeps answering during the image pull — so the overlay dismissed almost immediately, then the page died again when the old container actually stopped; (2) the in-progress self-update operation could never be recorded as completed: the finalize callback secret was regenerated per-process (the helper's POST always failed with 403 after the restart) and startup reconciliation expired the operation before the helper could finalize it; and (3) the transientdrydock-self-update-<timestamp>helper container carried no watch-exclusion label, so it flashed into the container list with watch-by-default enabled. The finalize secret is now per-operation, with its SHA-256 hash persisted on the operation row so the restarted process can validate the helper's callback; startup reconciliation grants fresh in-progress self-update operations a 10-minute grace window (with expiry as the bounded fallback if the helper dies); a new unauthenticatedGET /api/v1/self-update/{operationId}/statusendpoint reports the operation state while the session is unavailable mid-restart; the UI polls that endpoint during a self-update and only reloads once the operation reaches a terminal state (succeeded,rolled-back,failed, orexpired); and the helper container is labeleddd.watch=falseso it never appears in the watcher's container list. The UI also closes its SSE connection when self-update mode begins (after the ack is delivered) — the browser's built-in EventSource retry would otherwise reconnect to the new server, clear the overlay before the swap was committed, and leave the SPA running stale pre-update assets. Dry-run mode no longer shows the overlay at all: the UI notification is skipped and the no-op operation is marked terminal immediately instead of lingering in-progress.
Warning
Upgrade notes — behavioral changes, please read before updating. Releases 1.4.6 and the entire 1.5 line ship security-hardening fixes that change runtime behavior. These are not deprecations: there is no compatibility shim or grace period, so a previously-working deployment can change behavior on upgrade.
- OIDC login now requires
authorization_endpointin your provider's discovery metadata. The authorization-redirect allowlist no longer falls back to a broad same-origin match. Mainstream identity providers (Keycloak, Authentik, Authelia, Okta, Google, Entra/Azure AD, Zitadel, …) publish this field and are unaffected. If your/.well-known/openid-configurationdoes not advertiseauthorization_endpoint, OIDC sign-in will now fail closed — make sure the discovery document exposes it. - Unauthenticated rate-limit buckets now key on the TCP peer address instead of
X-Forwarded-For. Behind a reverse proxy (nginx / Traefik / Caddy), all unauthenticated clients now share a single bucket (the proxy's address), regardless ofDD_SERVER_TRUSTPROXY. Internet-facing or multi-user instances may begin to see unexpected429 Too Many Requestson unauthenticated endpoints. Authenticated requests are keyed per session and are unaffected. - HTTP-trigger
proxyURLs must now use thehttp://orhttps://scheme. Any other scheme (e.g.socks5://) is rejected at config load. Such values were previously accepted but only ever treated as an HTTP proxy — switch to anhttp(s)://proxy URL.
v1.4.6
Warning
Upgrade notes — behavioral changes, please read before updating. These are security hardening fixes, not deprecations: there is no compatibility shim or grace period, so a previously-working deployment can change behavior on upgrade.
- OIDC login now requires
authorization_endpointin your provider's discovery metadata. The authorization-redirect allowlist no longer falls back to a broad same-origin match. Mainstream identity providers (Keycloak, Authentik, Authelia, Okta, Google, Entra/Azure AD, Zitadel, …) publish this field and are unaffected. If your/.well-known/openid-configurationdoes not advertiseauthorization_endpoint, OIDC sign-in will now fail closed — make sure the discovery document exposes it. - Unauthenticated rate-limit buckets now key on the TCP peer address instead of
X-Forwarded-For. Behind a reverse proxy (nginx / Traefik / Caddy), all unauthenticated clients now share a single bucket (the proxy's address), regardless ofDD_SERVER_TRUSTPROXY. Internet-facing or multi-user instances may begin to see unexpected429 Too Many Requestson unauthenticated endpoints. Authenticated requests are keyed per session and are unaffected. - HTTP-trigger
proxyURLs must now use thehttp://orhttps://scheme. Any other scheme (e.g.socks5://) is rejected at config load. Such values were previously accepted but only ever treated as an HTTP proxy — switch to anhttp(s)://proxy URL.
[1.4.6] — 2026-06-12
Security maintenance release for the 1.4.x line.
Security
- Dependency security updates — Cleared all known advisories in the shipped backend (
app) and dashboard (ui) dependencies, including a critical arbitrary-code-execution issue inprotobufjsand high-severity SSRF / prototype-pollution / credential-leak issues inaxios, plus@grpc/grpc-js,fast-uri,fast-xml-parser,path-to-regexp,qs, and others. Both workspaces now report zeronpm auditvulnerabilities. No major dependency upgrades were needed — the Docker watcher stays ondockerode4.x (the flagged transitiveuuidadvisory affectsv3/v5/v6with abufargument and is not reachable throughdockerode'sv4()usage; it is pinned to a fixed release via an override). - OIDC authorization-redirect hardening — The OIDC provider now requires a strict authorization-endpoint match and no longer falls back to a broad same-origin allowlist when discovery metadata lacks an
authorization_endpoint. This prevents an attacker who controls a different path under a shared-origin identity provider from steering the authorization redirect to an attacker-controlled endpoint. - HTTP trigger SSRF guard — Proxy URLs configured for the HTTP trigger are now restricted to
http/httpsschemes, validated both at configuration time (schema) and at runtime, failing closed on any other scheme. - Rate-limit key spoofing fix — Unauthenticated rate-limit keys are now derived from the TCP peer address (
socket.remoteAddress) instead ofrequest.ip, so a client behind a trusted proxy cannot spoofX-Forwarded-Forto evade per-IP rate limits.
v1.5.0-rc.35
v1.5.0-rc.35
Full Changelog: v1.5.0-rc.34...v1.5.0-rc.35
[1.5.0-rc.35] — 2026-06-10
Fixed
-
False "update failed" notification when concurrent update requests race (#421). When a duplicate update request was rejected with HTTP 409 ("update already in progress") while the winning update was still in flight, the controller classified the conflict as a genuine failure — firing an "update failed" notification immediately followed by "updated successfully". The duplicate classifier now recognizes three benign signals instead of one: a recently-succeeded operation (as before), a 409 response whose body explicitly carries the active-update lock message ("Container update already queued/in progress" — authoritative even before the winner's state has propagated from a remote agent over SSE), and another active (queued or in-progress) operation for the same container and agent+watcher identity. The same reclassification now also covers the Docker-native rename path in
ContainerUpdateExecutor, which previously marked the duplicate failed before the outer classifier could run. -
Update operations could hang in-progress forever when deferred rollback reconciliation failed. The deferred reconciliation callback only logged a warning on error, leaving the operation permanently active and blocking all future updates for that container until restart. The operation is now terminalized as failed (self-update and already-terminal operations excluded, as elsewhere).
-
Spurious "update available" notifications around just-updated containers (#408 hardening). Three escape hatches in the post-update suppression mechanism are closed: suppression now keys on both the container ID and the watcher-scoped name, so the recreated container's new Docker ID can no longer dodge the check; the batch retry buffer consults suppression before re-queuing; and on startup the suppression set is re-seeded from update operations that succeeded within the last hour, so a controller restart between an update and the watcher's confirming scan no longer re-fires a stale notification. Suppression entries now also expire after one hour and are cleared on trigger deregistration, so containers deleted outright (or agents that never reconnect) can no longer leak entries for the life of the process.
-
Live log viewer returned 403 behind TLS-terminating reverse proxies even with
DD_SERVER_TRUSTPROXYset. WebSocket upgrades bypass Express, so the log-stream origin check never honored trust-proxy. It now compares the browser origin againstX-Forwarded-Host/X-Forwarded-Proto(first hop) when trust proxy is enabled — and remains byte-for-byte strict when it is not. -
Containers of permanently removed agents lingered in the store forever. Startup now prunes container rows whose
agentno longer matches any registered agent component. Rows of registered-but-currently-disconnected agents are untouched. -
"Updated successfully" toast fired while the new container was still starting (#290 follow-up). The toast settled on the old container's removal event during a recreate; it now waits for the replacement container's arrival (
replacementExpectedremovals are skipped, and the new container's ID rides thedd:update-appliedpayload asnewContainerId), closing the status gap between the last update activity and the success notification. The toast dedup TTL also gained a 20% margin over the server's SSE replay buffer to prevent a boundary duplicate on reconnect. -
Watcher enrichment failures that threw non-Error values leaked malformed entries into the container snapshot. Thrown non-Error values are now wrapped, counted as enrichment errors, and excluded.
-
GCR registry reported anonymous configurations as authenticated.
Gcr.getAuthPull()now returnsundefinedwithout credentials, matching the other providers. GHCR 404 detection now checks the axios response status instead of matching error-message strings.
Security
-
Command trigger no longer inherits the full process environment. User-authored command scripts previously received every
DD_*secret (registry tokens, notification tokens, agent secrets) viaprocess.env. The child environment is now built from a fixed allowlist (PATH,HOME,SHELL,USER,LANG,LC_ALL,TZ,TMPDIR,TMP,TEMP) plus the drydock-provided container variables. Scripts that legitimately need more can name additional variables with the newDD_ACTION_COMMAND_{name}_ENVoption (comma-separated). -
Hook commands can be restricted to an allowlist of binaries (
DD_HOOKS_ALLOWED_COMMANDS). With hooks enabled,dd.hook.pre/dd.hook.postlabels could invoke any binary on the image. The new comma-separated allowlist matches the hook command's first token (basename, or exact path for entries containing/); when unset, behavior is unchanged and a one-time warning recommends configuring it. -
HTTP trigger blocks cloud metadata endpoints. Requests resolving to link-local ranges (
169.254.0.0/16including169.254.169.254,fe80::/10,fd00:ec2::254) are rejected before sending — including IPv4-mapped and IPv4-compatible IPv6 spellings of those ranges (::ffff:169.254.169.254,::ffff:a9fe:a9fe,::169.254.169.254), which would otherwise slip past the literal-IP check. Private-network and localhost targets remain fully supported — they are the normal self-hosted case. The rare legitimate link-local target can opt out viaDD_NOTIFICATION_HTTP_{name}_ALLOWMETADATA=true.
Performance
-
Tag transform patterns are no longer recompiled in the sort hot path. Compiled RE2 transform patterns are cached per formula and tag candidates are transformed once before sorting instead of twice per comparison — previously ~3,000 compilations per 300-tag container per watch cycle.
-
Container normalization no longer deep-clones the entire container per registry call. The watcher now copies only the image/registry fields it mutates instead of
structuredCloneof labels and environment for every container every cycle. -
Update-operation retention pruning uses indexed status queries instead of materializing the whole collection every 100th mutation, and the default rejected-credential pattern in
BaseRegistryis compiled once at module load. An unusedupdatedAtcollection index no longer taxes every mutation.
Changed
- Trigger providers must implement
trigger()/triggerBatch(). The base implementations now throw instead of silently doing nothing, so a provider that forgets to override fails loudly. All 22 bundled providers already comply; this only affects out-of-tree forks.
v1.5.0-rc.34
v1.5.0-rc.34
Full Changelog: v1.5.0-rc.33...v1.5.0-rc.34
[1.5.0-rc.34] — 2026-06-07
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. -
Per-agent Home Assistant MQTT topic segmentation (
DD_NOTIFICATION_MQTT_<name>_HASS_AGENTTOPICSEGMENT, defaultfalse). When enabled, Drydock inserts anagent/<name>segment into every Home Assistant MQTT topic — per-container state topics, watcher-level count/update sensors, and watcher running-status sensors — for containers owned by a remote agent, so two agents that both use the default watcher namelocalno longer publish to (and overwrite) the same topics. Enabling it also scopes the watcher-level sensor counts and the discovery-entity cleanup per agent, fixing the Home Assistant facet of #386. Controller-local container topics are unchanged. Because it changes the Home Assistant entity IDs for agent-owned containers, it is opt-in for the v1.5.x line and targeted to become the default in v1.7.0 — see Deprecated. -
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.
-
Show/hide toggle on the login password field (commit
e086c5bc). The sign-in password input now has an eye / eye-slash button to reveal or mask what was typed, with an accessible label andtype="button"so it never submits the form. -
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 watched local and agent-owned containers 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. -
Post-start liveness grace window (
DD_UPDATE_POST_START_LIVENESS_GRACE_MS). After Dockerstart()returns, Drydock waits this many milliseconds and then re-inspects the new container. If the container has already exited, the lifecycle throws and the existing rollback machinery takes over — catching containers that exit immediately after an update (bad command, broken entrypoint, missing dependency) that would otherwise be recorded as a successful update. Default2000ms. Set to0to disable the check entirely. Values between 1 and 99 ms are rejected at startup; the minimum non-zero value is 100 ms. -
Recovery-boot concurrency cap (
DD_UPDATE_RECOVERY_BOOT_CONCURRENCY). When Drydock restarts after a crash it finds queued update operations left from the previous run and resumes them. This variable bounds how many are dispatched in parallel during that recovery sweep. Default4. Values of0are rejected at startup (minimum is 1). -
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 it...
v1.5.0-rc.33
v1.5.0-rc.33
Full Changelog: v1.5.0-rc.32...v1.5.0-rc.33
[1.5.0-rc.33] — 2026-06-06
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. -
Per-agent Home Assistant MQTT topic segmentation (
DD_NOTIFICATION_MQTT_<name>_HASS_AGENTTOPICSEGMENT, defaultfalse). When enabled, Drydock inserts anagent/<name>segment into every Home Assistant MQTT topic — per-container state topics and watcher-level sensor topics — for containers owned by a remote agent, so two agents that both use the default watcher namelocalno longer publish to (and overwrite) the same topics. Enabling it also scopes the watcher-level sensor counts and the discovery-entity cleanup per agent, fixing the Home Assistant facet of #386. Controller-local container topics are unchanged. Because it changes the Home Assistant entity IDs for agent-owned containers, it is opt-in for the v1.5.x line and targeted to become the default in v1.7.0 — see Deprecated. -
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.
-
Show/hide toggle on the login password field (commit
e086c5bc). The sign-in password input now has an eye / eye-slash button to reveal or mask what was typed, with an accessible label andtype="button"so it never submits the form. -
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. -
Post-start liveness grace window (
DD_UPDATE_POST_START_LIVENESS_GRACE_MS). After Dockerstart()returns, Drydock waits this many milliseconds and then re-inspects the new container. If the container has already exited, the lifecycle throws and the existing rollback machinery takes over — catching containers that exit immediately after an update (bad command, broken entrypoint, missing dependency) that would otherwise be recorded as a successful update. Default2000ms. Set to0to disable the check entirely. Values between 1 and 99 ms are rejected at startup; the minimum non-zero value is 100 ms. -
Recovery-boot concurrency cap (
DD_UPDATE_RECOVERY_BOOT_CONCURRENCY). When Drydock restarts after a crash it finds queued update operations left from the previous run and resumes them. This variable bounds how many are dispatched in parallel during that recovery sweep. Default4. Values of0are rejected at startup (minimum is 1). -
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 (commit [`cf77728...
v1.5.0-rc.32
v1.5.0-rc.32
Full Changelog: v1.5.0-rc.31...v1.5.0-rc.32
[1.5.0-rc.32] — 2026-06-06
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. -
Per-agent Home Assistant MQTT topic segmentation (
DD_NOTIFICATION_MQTT_<name>_HASS_AGENTTOPICSEGMENT, defaultfalse). When enabled, Drydock inserts anagent/<name>segment into every Home Assistant MQTT topic — per-container state topics and watcher-level sensor topics — for containers owned by a remote agent, so two agents that both use the default watcher namelocalno longer publish to (and overwrite) the same topics. Enabling it also scopes the watcher-level sensor counts and the discovery-entity cleanup per agent, fixing the Home Assistant facet of #386. Controller-local container topics are unchanged. Because it changes the Home Assistant entity IDs for agent-owned containers, it is opt-in for the v1.5.x line and targeted to become the default in v1.7.0 — see Deprecated. -
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.
-
Show/hide toggle on the login password field (commit
e086c5bc). The sign-in password input now has an eye / eye-slash button to reveal or mask what was typed, with an accessible label andtype="button"so it never submits the form. -
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. -
Post-start liveness grace window (
DD_UPDATE_POST_START_LIVENESS_GRACE_MS). After Dockerstart()returns, Drydock waits this many milliseconds and then re-inspects the new container. If the container has already exited, the lifecycle throws and the existing rollback machinery takes over — catching containers that exit immediately after an update (bad command, broken entrypoint, missing dependency) that would otherwise be recorded as a successful update. Default2000ms. Set to0to disable the check entirely. Values between 1 and 99 ms are rejected at startup; the minimum non-zero value is 100 ms. -
Recovery-boot concurrency cap (
DD_UPDATE_RECOVERY_BOOT_CONCURRENCY). When Drydock restarts after a crash it finds queued update operations left from the previous run and resumes them. This variable bounds how many are dispatched in parallel during that recovery sweep. Default4. Values of0are rejected at startup (minimum is 1). -
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 (commit [`cf77728...
v1.5.0-rc.31
v1.5.0-rc.31
Full Changelog: v1.5.0-rc.30...v1.5.0-rc.31
[1.5.0-rc.31] — 2026-06-05
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. -
Per-agent Home Assistant MQTT topic segmentation (
DD_NOTIFICATION_MQTT_<name>_HASS_AGENTTOPICSEGMENT, defaultfalse). When enabled, Drydock inserts anagent/<name>segment into every Home Assistant MQTT topic — per-container state topics and watcher-level sensor topics — for containers owned by a remote agent, so two agents that both use the default watcher namelocalno longer publish to (and overwrite) the same topics. Enabling it also scopes the watcher-level sensor counts and the discovery-entity cleanup per agent, fixing the Home Assistant facet of #386. Controller-local container topics are unchanged. Because it changes the Home Assistant entity IDs for agent-owned containers, it is opt-in for the v1.5.x line and targeted to become the default in v1.7.0 — see Deprecated. -
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.
-
Show/hide toggle on the login password field (commit
e086c5bc). The sign-in password input now has an eye / eye-slash button to reveal or mask what was typed, with an accessible label andtype="button"so it never submits the form. -
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. -
Post-start liveness grace window (
DD_UPDATE_POST_START_LIVENESS_GRACE_MS). After Dockerstart()returns, Drydock waits this many milliseconds and then re-inspects the new container. If the container has already exited, the lifecycle throws and the existing rollback machinery takes over — catching containers that exit immediately after an update (bad command, broken entrypoint, missing dependency) that would otherwise be recorded as a successful update. Default2000ms. Set to0to disable the check entirely. Values between 1 and 99 ms are rejected at startup; the minimum non-zero value is 100 ms. -
Recovery-boot concurrency cap (
DD_UPDATE_RECOVERY_BOOT_CONCURRENCY). When Drydock restarts after a crash it finds queued update operations left from the previous run and resumes them. This variable bounds how many are dispatched in parallel during that recovery sweep. Default4. Values of0are rejected at startup (minimum is 1). -
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 (commit [`cf77728...
v1.5.0-rc.30
v1.5.0-rc.30
Full Changelog: v1.5.0-rc.29...v1.5.0-rc.30
[1.5.0-rc.30] — 2026-06-05
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. -
Per-agent Home Assistant MQTT topic segmentation (
DD_NOTIFICATION_MQTT_<name>_HASS_AGENTTOPICSEGMENT, defaultfalse). When enabled, Drydock inserts anagent/<name>segment into every Home Assistant MQTT topic — per-container state topics and watcher-level sensor topics — for containers owned by a remote agent, so two agents that both use the default watcher namelocalno longer publish to (and overwrite) the same topics. Enabling it also scopes the watcher-level sensor counts and the discovery-entity cleanup per agent, fixing the Home Assistant facet of #386. Controller-local container topics are unchanged. Because it changes the Home Assistant entity IDs for agent-owned containers, it is opt-in for the v1.5.x line and targeted to become the default in v1.7.0 — see Deprecated. -
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.
-
Show/hide toggle on the login password field (commit
e086c5bc). The sign-in password input now has an eye / eye-slash button to reveal or mask what was typed, with an accessible label andtype="button"so it never submits the form. -
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. -
Post-start liveness grace window (
DD_UPDATE_POST_START_LIVENESS_GRACE_MS). After Dockerstart()returns, Drydock waits this many milliseconds and then re-inspects the new container. If the container has already exited, the lifecycle throws and the existing rollback machinery takes over — catching containers that exit immediately after an update (bad command, broken entrypoint, missing dependency) that would otherwise be recorded as a successful update. Default2000ms. Set to0to disable the check entirely. Values between 1 and 99 ms are rejected at startup; the minimum non-zero value is 100 ms. -
Recovery-boot concurrency cap (
DD_UPDATE_RECOVERY_BOOT_CONCURRENCY). When Drydock restarts after a crash it finds queued update operations left from the previous run and resumes them. This variable bounds how many are dispatched in parallel during that recovery sweep. Default4. Values of0are rejected at startup (minimum is 1). -
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 (commit [`cf77728...
v1.5.0-rc.29
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 a `...