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.