Skip to content
ConfigurationContainer Actions

Update Eligibility & Blockers

Why a container with an available update isn't auto-updating, and how to clear the gate.

Overview

When a container has an available update, drydock evaluates it against a stack of policy and trigger gates before the matching docker / dockercompose action trigger fires. Any active gate becomes a blocker. The result is exposed on every container payload as updateEligibility; the UI summarizes it on the Update action and explains it in the container's Update Status panel.

A container is eligible when an update exists and no blocker applies. What happens next also depends on the server-wide Update mode.

Global update mode

Choose the mode under Settings → General → Update mode. The setting applies to the whole Drydock server, not just the current browser or user.

ModeDetection and notificationsManual UI/API updatesAutomatic action triggers
notifyEnabledRefusedSuppressed
manualEnabledEnabled, subject to eligibilitySuppressed
autoEnabledEnabled, subject to eligibilityEnabled, subject to trigger configuration and eligibility

Fresh installations default to manual. When an existing installation upgrades from a version with no updateMode setting, Drydock migrates it to auto so configured automatic updates do not silently stop. Changing the mode later takes effect server-wide.

In notify mode the Update Status panel leads with Notifications only — Drydock won't apply updates and keeps diagnostic conditions behind Show details. Attempts to update through Drydock are rejected; continue to use your external update tooling.

Where blockers appear

  • Container table and cards — the Update action carries one compact state: ready, soft-blocked (manual override available), or hard-blocked (locked). It does not repeat every reason on the row.
  • Side panel and container full-page view — the Update Status panel shows one plain-language summary followed by the active conditions. Actionable conditions open the relevant in-app update-policy, operation-history, security, or audit surface. Conditions backed by environment variables or container labels link to the corresponding configuration documentation because those values do not have an in-app editor. Time-limited conditions retain their countdown and exact lift time.
  • Dashboard — Recent Updates widget — uses the same compact Update-action state as the container list.

If a container is eligible but an automatic update does not run, check that the global mode is auto and that a matching docker / dockercompose action trigger is configured to fire for that container.

Reasons reference

Reason codeCondition labelSeverityWhy it firesHow to clear it
security-scan-blockedSecurity blockedHardThe candidate image scan or the current container's existing scan detected vulnerabilities exceeding the configured threshold. Both halt the update unless DD_SECURITY_GATE_RELATIVE=true produced a recorded no-worse-than-current pass for this candidate. Relative comparison remains blocked when the current scan is missing, failed, stale, or any severity count increased.Fix the vulnerability upstream, review the relative-gate evidence, lower the scan threshold, or use force-update to override for one cycle.
last-update-rolled-backRolled backHardThe previous update attempt for this container was rolled back, and the same candidate image (digest if available, otherwise tag) is still the pending target. Drydock blocks re-applying the identical target so a known-bad update isn't retried automatically; a different, newer image is never blocked.Wait for a newer image to be released, or set dd.update.rollback-gate=off on the container to override the gate.
snoozedSnoozedSoftContainer's update policy has snoozeUntil set to a future timestamp.Clear the snooze from the container row's Update policy menu, or wait for the lift date shown in the panel.
skip-tagTag skippedSoftThe remote tag is in the container's skipTags list.Remove the tag from the skip list in the Update policy menu.
skip-digestDigest skippedSoftThe remote digest is in the container's skipDigests list.Remove the digest from the skip list in the Update policy menu.
maturity-not-reachedMaturingSoftContainer's effective maturityMode is mature and the update has not yet aged past maturityMinAgeDays days. The blocker message identifies the active policy tier (env, label, or override). The gate measures elapsed time from the earliest known point the update existed: for Docker Hub and GHCR (including lscr.io), Drydock uses the registry's real publish timestamp (last_updated / updated_at), so an image already soaked in the registry longer than the threshold clears immediately on first detection. For all other registries (ACR, ECR, GCR, Quay, Harbor, etc.) the OCI image-config created field is a build date, not a reliable push date, so the gate falls back to updateDetectedAt — Drydock's own wall-clock time when it first recorded the pending update. The gate clock survives container recreation as long as the same candidate remains pending; a changed tag, digest, or mutable-tag image restarts the soak. The candidate remains visible while held, and the detail view shows a live countdown plus the exact local liftableAt date and time.Wait for the gate to lift, use Update Now, change the active label/watcher setting, add a UI override, or Revert an override to the declarative value.
threshold-not-reachedBelow thresholdSoftThe matching trigger's threshold (e.g. major) is more restrictive than the detected update kind (e.g. minor).Lower the trigger's THRESHOLD env var, or wait for an update that meets it.
trigger-excludedTrigger excludedSoftContainer's dd.action.exclude (or legacy dd.trigger.exclude) label matches the candidate trigger.Remove the exclusion from the container label, or rename the trigger so the rule no longer matches.
trigger-not-includedTrigger filteredSoftAction triggers default to AUTO=oninclude, so the container needs an explicit dd.action.include label naming the trigger. The label is missing or doesn't list this trigger.Add dd.action.include=<trigger-name> to the container's labels, or set DD_ACTION_DOCKER[COMPOSE]_<NAME>_AUTO=true on the trigger to fire on every matching container.
agent-mismatchAgent mismatchHardA docker / dockercompose trigger exists, but its agent doesn't match the container's agent.Configure a trigger on the container's agent, or move the container to the trigger's agent.
no-update-trigger-configuredNo triggerHardNo docker or dockercompose action trigger is configured at all.Configure DD_ACTION_DOCKER_<NAME>_* or DD_ACTION_DOCKERCOMPOSE_<NAME>_*. See Triggers — Docker and Triggers — Docker Compose.
self-update-unavailableSelf-update unavailableHardThe container is Drydock itself and an update is available, but self-update can run neither over a bind-mounted Docker socket nor a TCP Docker host.Bind-mount /var/run/docker.sock into the Drydock container, or point the watcher at a TCP Docker host. See Self-Update.
rollback-containerRollbackHardContainer was created during a previous update as a rollback artifact and isn't itself updateable.Not actionable — remove the rollback container manually if no longer needed.
active-operationIn progressHardAn update is queued or already running for this container.Wait for completion.
no-update-availableUp to dateHard/internalNo newer image was detected. Internal short-circuit; shown as the panel's summary rather than as an actionable condition.N/A
maintenance-window-closedMaintenance window closedSoftAutomatic-update dispatch evaluated the container while its watcher's maintenance window was closed. Drydock defers automatic updates outside the window regardless of whether the update was found by cron, manual watch, webhook, or post-update fast resync. Container-list and SSE responses enrich this live watcher state for both local and agent-owned containers. Manual UI/API updates pass no maintenance-window state and are not gated by this blocker.Wait for the next maintenance window or adjust DD_WATCHER_{watcher_name}_MAINTENANCE_WINDOW / _TZ.
The Update Status panel keeps no-update-available as the top-level Up to date state and avoids duplicating active-operation when an in-progress operation badge already communicates the same state elsewhere.

Common scenarios

"Auto-update isn't firing on any of my compose-managed containers"

Almost always trigger-not-includeddocker and dockercompose action triggers default to AUTO=oninclude. Every container you want auto-updated needs a dd.action.include label that names the matching trigger.

# drydock env
- DD_ACTION_DOCKERCOMPOSE_MYSTACK_FILE=/drydock/mystack/compose.yaml

# in mystack/compose.yaml — every service that should auto-update
services:
  app:
    labels:
      - dd.action.include=mystack

If you want every container in a stack to auto-update without per-container opt-in, set AUTO=true on the trigger instead:

- DD_ACTION_DOCKERCOMPOSE_MYSTACK_AUTO=true

"Auto-update fired before, but not for this minor release"

Likely threshold-not-reached — the trigger's THRESHOLD is set to something stricter than the detected update kind. Common cases: THRESHOLD=major will skip minor/patch/digest updates; THRESHOLD=patch will skip digest-only updates.

"All my containers show 'Maturing' even though I never set a policy"

That blocker only fires when the effective maturityMode === 'mature'. Check the blocker text and policy editor for its source: a dd.updatePolicy.maturityMode=mature container label, the watcher's DD_WATCHER_{watcher_name}_MATURITY_MODE=mature default, or a UI/API override. With none of those configured, the built-in mode is all and no maturity gate applies.

"Update Status says 'Agent mismatch'"

The trigger and the container are bound to different agents. In the controller-agent topology, an action trigger only updates containers on the agent it runs on. Either configure an additional trigger on the container's agent, or merge the topology.

API

The updateEligibility object is returned on every container in GET /api/v1/containers and pushed via SSE on changes. Both paths enrich the owning watcher's live maintenance-window state, including the masked watcher state reported by remote agents. Shape:

{
  "eligible": false,
  "blockers": [
    {
      "reason": "trigger-not-included",
      "severity": "soft",
      "message": "Trigger not matched by container label dd.action.include='undefined'.",
      "actionable": true,
      "actionHint": "Adjust the `dd.action.include` / `dd.action.exclude` labels on the container.",
      "details": {
        "triggerInclude": null,
        "triggerId": "dockercompose.mystack"
      }
    }
  ],
  "evaluatedAt": "2026-04-26T14:44:00.000Z"
}

Fields:

  • eligibletrue only when an update exists and no blockers apply.
  • blockers[].reason — one of the codes in the reasons reference above. Stable identifier; safe to switch on programmatically.
  • blockers[].severity'hard' or 'soft'. A hard blocker prevents manual updates too: the API rejects the update request and the UI disables the Update button (e.g. security-scan-blocked, rollback-container). A soft blocker only defers automatic dispatch — manual updates still succeed, with the UI showing a confirm modal that lists the soft blockers (e.g. maturity-not-reached, snoozed).
  • blockers[].message — human-readable summary, with concrete values interpolated (tag, threshold, days remaining, etc.).
  • blockers[].actionabletrue when a user can clear the blocker; false for terminal states (rollback-container, active-operation, no-update-available).
  • blockers[].actionHint — short next-step suggestion. Used as the second line in tooltips and the Action field on full-page cards.
  • blockers[].liftableAt — ISO timestamp when a time-limited blocker (snooze, maturity) will clear automatically.
  • blockers[].details — reason-specific payload (skipped tag, threshold value, agent IDs, etc.). Schema is per-reason and may grow over time.
  • evaluatedAt — ISO timestamp of the evaluation. Recomputed on every container update and watcher run.

For maturity blockers, the UI treats liftableAt as the gate eligibility time. It is not a guaranteed deployment timestamp: automatic actions run after a watcher observes the now-mature candidate, and maintenance windows, security checks, trigger filters, queues, or an offline agent can delay application further.

  • Container Actions — the surrounding action system.
  • TriggersTHRESHOLD, AUTO, and per-trigger INCLUDE / EXCLUDE env vars.
  • Watchers — Labelsdd.action.include / dd.action.exclude and the deprecated dd.trigger.* aliases.