Skip to content
ConfigurationContainer Actions

Container Actions

Start, stop, restart, update, and delete containers directly from drydock.

Overview

Drydock provides direct container management actions through both the UI and API. These actions let you control containers without leaving the drydock interface.

Available actions

ActionAPI endpointDescription
StartPOST /api/v1/containers/:id/startStart a stopped container
StopPOST /api/v1/containers/:id/stopStop a running container
RestartPOST /api/v1/containers/:id/restartRestart a container
UpdatePOST /api/v1/containers/:id/updatePull new image and recreate container
DeleteDELETE /api/v1/containers/:idRemove the container from Drydock tracking; this does not delete the runtime container, and it may reappear after rediscovery

Feature flags

Container actions can be individually toggled with environment variables:

Env varRequiredDescriptionSupported valuesDefault value when missing
DD_SERVER_FEATURE_CONTAINERACTIONS⚪Enable start, stop, restart, and update actionstrue, falsetrue
DD_SERVER_FEATURE_DELETE⚪Enable container deletiontrue, falsetrue
When a feature flag is disabled, the corresponding buttons are hidden in the UI and the API endpoints return 403 Forbidden.
Container action endpoints currently use an authentication-only, single-operator trust model. Any authenticated user can start, stop, restart, update, or delete any container. Run drydock behind trusted SSO/access controls until fine-grained RBAC is available.

Trigger configuration aliases

Container updates are executed by the Docker and Docker Compose action triggers. The Command trigger is also classified as an action trigger for configuration taxonomy and defaults, but it runs an operator-defined command and does not enter Drydock's Docker update lifecycle queue. These triggers can be configured with two environment variable prefixes that are treated identically:

PrefixStatus
DD_ACTION_*Current — recommended for action triggers
DD_NOTIFICATION_*Current — recommended for notification triggers
DD_TRIGGER_*Removed in v1.7.0

When the same trigger is defined under multiple prefixes, the merge priority is: DD_NOTIFICATION_* > DD_ACTION_*.

Container labels follow the same pattern: dd.action.include / dd.action.exclude replaced the removed dd.trigger.include / dd.trigger.exclude.

Action triggers (Docker, Docker Compose, Portainer, Command) default to AUTO=oninclude, meaning they only auto-update containers that have an explicit dd.action.include label matching the trigger name. Notification triggers default to AUTO=true.
dd.action.include / dd.action.exclude are hard blockers. A container with a non-matching include or an active exclude label rejects both automatic and manual updates — the Update button is locked and the API returns 409 for a manual update request. If you need a container to stay manually updatable while excluded from automatic dispatch, use AUTO=onauto with dd.action.include (or dd.action.auto) instead of relying on the old soft-blocker behavior.

See Triggers for full trigger configuration reference and the migration CLI for automated prefix rewriting.

Update action

The update action triggers the Docker or Docker Compose trigger for the selected container. It will:

  1. Pull the latest image
  2. Backup the current image
  3. Run lifecycle hooks (if configured)
  4. Stop and remove the current container
  5. Create and start the new container
  6. Monitor health (if auto-rollback is enabled)

See Lifecycle Hooks and Backup & Rollback for related configuration.

Dry-run mode

Set DD_ACTION_DOCKER_<name>_DRYRUN=true (or the equivalent Docker Compose action variable) to pull and validate candidate images without replacing containers. Dry-run is intentionally conspicuous:

  • the Triggers view marks the action with a yellow Dry-run mode badge;
  • the container Update Status panel names the effective dry-run trigger;
  • Update now is relabeled Preview only with an explanation of the remaining pull side effect;
  • each prevented replacement is logged at WARN; and
  • History records update-applied-dryrun, distinct from a completed replacement.
Dry-run still pulls the candidate image. It does not stop or replace the running container. Set the action trigger's DRYRUN=false and recreate Drydock before expecting updates to be applied.

Update concurrency

By default, drydock updates one container at a time per action. Set DD_UPDATE_CONCURRENCY to update more containers at once within a fleet-wide update — a scan-triggered digest batch or a manual "Update All" — while still respecting the per-container and per-compose-project locks described under Global concurrency cap below.

Env varRequiredDescriptionDefault
DD_UPDATE_CONCURRENCY⚪Max simultaneous update executions per action instance (docker, dockercompose, command)1
DD_ACTION_<TYPE>_<NAME>_CONCURRENCY⚪Per-action override; wins over DD_UPDATE_CONCURRENCY for that one action instanceunset (inherits the global value)

Semantics:

  • 1 (default) → updates through this action run one at a time — drydock's original behavior.
  • N (positive integer) → up to N container updates run at once through this action.
  • 0 or a non-integer value → drydock fails fast at startup with a descriptive error.

This gate is per action, not per whole update lifecycle: it bounds how many container updates docker, dockercompose, or command run concurrently, regardless of what triggered them — a scan-triggered digest batch, a manual bulk "Update All", a dependency-chain update, or startup recovery. DD_ACTION_<TYPE>_<NAME>_CONCURRENCY follows the same <type>.<name> shape as any other per-action override, so DD_ACTION_DOCKERCOMPOSE_BACKUPS_CONCURRENCY overrides the dockercompose action registered as backups.

DD_UPDATE_CONCURRENCY and DD_UPDATE_MAX_CONCURRENT (below) are independent gates layered on top of each other; the effective concurrency is whichever is tighter. DD_UPDATE_CONCURRENCY bounds one action's own concurrent executions. DD_UPDATE_MAX_CONCURRENT bounds the whole controller's simultaneous update lifecycles, across every action.
services:
  drydock:
    image: codeswhat/drydock
    environment:
      # Update up to 4 containers at once through the docker action.
      - DD_UPDATE_CONCURRENCY=4
      # Keep the "backups" compose stack fully serial regardless of the global setting.
      - DD_ACTION_DOCKERCOMPOSE_BACKUPS_CONCURRENCY=1

Compose-file-once ordering

Docker Compose groups the containers in one update request by compose file before applying the concurrency limit: DD_UPDATE_CONCURRENCY (or its override) bounds how many compose files update at once, not how many containers within a shared file update at once. Every container backed by the same compose file still updates through one docker compose up invocation, in the same order it always has.

Fleet update progress in the UI

While a fleet-wide update batch (two or more containers) is in flight, the container list shows a progress banner naming how many containers have finished and which ones are currently running, driven by the same operation events the per-group "Update All" progress already used.

Global concurrency cap

Two layers of serialization always apply, regardless of the DD_UPDATE_CONCURRENCY setting above:

  • Per-container lock — prevents the same container from being updated twice in parallel.
  • Per-compose-project lock — serializes services within the same compose stack so docker compose up orchestration is safe.

If you need a broader limit — for example, to stay inside Docker Hub pull-rate budgets or to keep host restart load predictable — set DD_UPDATE_MAX_CONCURRENT:

Env varRequiredDescriptionDefault
DD_UPDATE_MAX_CONCURRENT⚪Max simultaneous update lifecycles across the entire controller instance0 (unlimited)

Semantics:

  • 0 or absent → unlimited (default; no change from current behavior on upgrade).
  • 1 → strictly serial; only one update lifecycle runs at a time.
  • N (positive integer) → at most N update lifecycles run concurrently.
  • Negative or non-integer values → drydock fails fast at startup with a descriptive error.

The global cap layers on top of the per-container and per-compose-project locks — it does not replace them. A compose stack already serializes its own members via the per-project lock; the global cap is an additional gate that limits how many stacks (or bare containers) can update at the same time.

Operations waiting on the global cap remain in queued status, which the UI already renders correctly as the Queued → Updating → Updated progression.

Scope: per controller instance. Distributed agent hosts have independent counters by design — there is no cross-host coordination.

Self-update operations (drydock updating itself) bypass the global semaphore, but first wait for already-admitted update lifecycles to drain through a fair exclusive gate. Once admitted, the self-update blocks new container update lifecycles in the outgoing controller until it restarts. Infrastructure-mode updates keep their global-cap bypass without taking the exclusive gate.
services:
  drydock:
    image: codeswhat/drydock
    environment:
      # Allow at most 3 container updates to run simultaneously.
      - DD_UPDATE_MAX_CONCURRENT=3

Health-gate SSE heartbeat

When drydock waits for a new container to pass its health gate (images with long healthcheck intervals, like vaultwarden's 60 s check), the UI receives no SSE events between the health-gate phase (start of wait) and health-gate-passed (end of wait). The health-gate heartbeat fixes this by re-emitting the same health-gate phase event at a configurable interval, keeping the UI informed in near-real-time without relying on REST reconciliation.

Env varRequiredDescriptionDefault
DD_UPDATE_HEALTH_GATE_HEARTBEAT_MS⚪Interval (ms) between health-gate SSE heartbeats during container health polling10000 (10 s)

Semantics:

  • Absent or empty → use the 10 s default.
  • 0 → disable heartbeats entirely (the UI will only receive the start health-gate event and the eventual health-gate-passed or rollback event).
  • Positive integer ≥ 1000 → emit a heartbeat every N milliseconds.
  • Values below 1000 or non-integer values → drydock fails fast at startup with a descriptive error.

Heartbeats re-emit phase: 'health-gate' (the same phase already displayed by the UI) — they do not introduce a new phase and cannot regress phase rank. The heartbeat stops immediately when the health gate resolves (success, timeout, or unhealthy), so it cannot race the terminal health-gate-passed event.

services:
  drydock:
    image: codeswhat/drydock
    environment:
      # Emit a health-gate heartbeat every 5 seconds.
      - DD_UPDATE_HEALTH_GATE_HEARTBEAT_MS=5000

Post-start liveness check

After Docker's start() call returns, the new container has been launched but may still exit immediately (bad command, broken entrypoint, missing dependency). For images without a configured HEALTHCHECK, the existing health gate is skipped — meaning an immediate exit-on-launch was being silently treated as a successful update. The post-start liveness check closes this gap: drydock sleeps a brief grace period after start, then re-inspects the container. If the container is no longer running, the update is treated as a failure and the existing rollback machinery takes over.

Env varRequiredDescriptionDefault
DD_UPDATE_POST_START_LIVENESS_GRACE_MS⚪Grace period (ms) to wait after container start before re-inspecting for an immediate exit. Set to 0 to disable the check entirely.2000 (2 s)

Semantics:

  • Absent or empty → use the 2 s default.
  • 0 → disable the post-start liveness check entirely (the update proceeds as before without the re-inspect).
  • Positive integer ≥ 100 → wait this many milliseconds, then re-inspect.
  • Values between 1 and 99 (inclusive) → drydock fails fast at startup with a descriptive error (minimum enforced to prevent accidental near-zero values that would be indistinguishable from 0).

The check is conservative: inspect failures are warned-and-tolerated (Drydock cannot prove the container died) and the check only converts a "definitively exited" state into a failure. Containers that pass the grace window continue to the health gate as normal.

services:
  drydock:
    image: codeswhat/drydock
    environment:
      # Extend grace period to 5 seconds for slow-starting containers.
      - DD_UPDATE_POST_START_LIVENESS_GRACE_MS=5000

Why isn't my container auto-updating?

Drydock evaluates each container against a stack of policy and trigger gates and reports the result on the Update action and as updateEligibility on the API payload. Open the container's Update Status panel for the plain-language summary, active conditions, and direct links to clear actionable blockers such as Below threshold, Trigger filtered, No trigger, Agent mismatch, or Snoozed. The server-wide Settings → General → Update mode chooses notifier-only, manual-only, or automatic application. See Update Eligibility & Blockers for the complete reason and mode reference.

Preview

Before updating, you can preview what will change using the dry-run endpoint:

POST /api/v1/containers/:id/preview

This returns the proposed changes without executing them. Preview failures use stable code and message fields instead of a generic failure. Registry and trigger configuration errors also include an in-app action link when Drydock can identify a safe next step.

Metrics

All container actions are tracked via Prometheus counters. See Monitoring for details.

Example — disable all actions

services:
  drydock:
    image: codeswhat/drydock
    environment:
      - DD_SERVER_FEATURE_CONTAINERACTIONS=false
      - DD_SERVER_FEATURE_DELETE=false