Skip to content
ConfigurationLifecycle Hooks

Lifecycle Hooks

Run custom commands before or after container updates.

Overview

Lifecycle hooks let you run shell commands before or after a container is updated. Common use cases:

  • Stop dependent services before updating
  • Run database migrations after updating
  • Send custom notifications
  • Create network resources or volumes

Container labels

LabelTypeDefaultDescription
dd.hook.prestringShell command to execute before the update
dd.hook.poststringShell command to execute after the update
dd.hook.pre.abortbooleantrueAbort the update if the pre-hook exits with a non-zero code
dd.hook.timeoutnumber (ms)60000Maximum execution time for each hook
Legacy wud.hook.* labels are ignored as of v1.6. Rename them to dd.hook.* before upgrading, or use node dist/index.js config migrate to rewrite compose files.

Hook enablement and allowlist

Env varRequiredDescriptionSupported valuesDefault
DD_HOOKS_ENABLEDEnable execution of lifecycle hook labels (dd.hook.*)true, falsefalse
DD_HOOKS_ALLOWED_COMMANDSComma-separated list of allowed first-token binary names or pathse.g. curl,sh,/usr/local/bin/notify.sh(unset — see below)
Hooks sourced from labels are skipped by default. Set DD_HOOKS_ENABLED=true to opt in.
Command allowlist (recommended): When DD_HOOKS_ENABLED=true and DD_HOOKS_ALLOWED_COMMANDS is not set, drydock logs a one-time warning recommending you configure the allowlist. Any hook that passes the syntax validator runs. When DD_HOOKS_ALLOWED_COMMANDS is set, drydock checks the first token of each hook command against the list before executing. Hooks whose binary is not on the list are rejected with a clear error and never executed.

Allowlist matching rules

  • No slash in entry (curl, sh): the basename of the hook command's first token is compared. curl matches both curl and /usr/bin/curl.
  • Slash in entry (/usr/local/bin/notify.sh): an exact full-path match is required. The basename alone is not sufficient.

Example:

DD_HOOKS_ALLOWED_COMMANDS=curl,/usr/local/bin/notify.sh

This permits curl https://… and /usr/local/bin/notify.sh, but not notify.sh or /other/path/curl.

Hook environment variables

When a hook runs, drydock injects environment variables describing the update:

VariableDescriptionExample
DD_CONTAINER_NAMEContainer namemyapp
DD_CONTAINER_IDContainer IDabc123def456
DD_IMAGE_NAMEImage name (without registry prefix)myorg/myapp
DD_IMAGE_TAGCurrent image tag1.2.3
DD_UPDATE_KINDUpdate typetag or digest
DD_UPDATE_FROMCurrent tag or digest1.2.3
DD_UPDATE_TONew tag or digest1.3.0

Injected hook environment values are sanitized before execution. Control characters and shell metacharacters `, $, ;, &, |, <, >, (, and ) are replaced with _, matching the command trigger's environment hardening. This protects hook scripts that expand Drydock-provided values but does not make arbitrary hook commands safe; keep hook commands trusted and quote variables in your scripts.

Command validation

Hook commands are validated against a restricted shell-safe grammar. Commands containing any of the following are rejected when the hook runs, with a clear error:

  • Command substitution: $(...) or backticks (`)
  • Output or input redirections: >, >>, <
  • Command chaining metacharacters: ;, &&, ||, |

Simple positional arguments and environment variable references that do not use substitution are allowed. Use a wrapper script (mounted into the container) for complex logic.

Execution details

  • Hooks run inside the drydock container using /bin/sh -c only when DD_HOOKS_ENABLED=true
  • Combined stdout and stderr output is capped at 10 KB
  • An exit code of 0 is treated as success; any non-zero code is a failure
  • If a hook exceeds its timeout, it is killed and treated as a failure (exit code 1)
  • Hooks are NOT executed during self-update

Threat model (label-sourced commands)

Hooks are sourced from container labels and then executed by drydock. Treat hook labels as a code-execution boundary.

  • Trust boundary: Container labels (dd.hook.*) cross into the drydock runtime command executor.
  • Trust boundary: Hook execution can cross from drydock into Docker host control when the socket is mounted.
  • High-value assets: Docker socket access, mounted credentials/secrets, and network-reachable internal services.
  • Attacker capability: Anyone who can modify deployed container labels (Compose manifests, swarm specs, API-driven container creation) can inject hook commands.
  • Abuse path: A malicious label can execute arbitrary shell via /bin/sh -c, including secret exfiltration or unauthorized Docker API actions.
  • Abuse path: Expensive hooks can degrade availability (for example long-running or fork-heavy commands), bounded by dd.hook.timeout.
  • Mitigation: Restrict who can modify container specs/labels and protect deployment pipelines.
  • Mitigation: Run drydock with least privilege; mount /var/run/docker.sock only when required.
  • Mitigation: Keep dd.hook.pre.abort=true when you want fail-closed behavior on pre-hook errors.
  • Mitigation: Monitor audit events (hook-configured, hook-pre-*, hook-post-*) for unauthorized hook changes and executions.

Pre-hook abort behavior

When dd.hook.pre.abort is true (the default), a failed pre-hook cancels the update entirely. Set it to false if you want the update to proceed regardless of hook outcome.

Audit events

EventDescription
hook-configuredHook labels were detected for the update lifecycle
hook-pre-successPre-hook completed successfully
hook-pre-failedPre-hook exited non-zero or timed out
hook-post-successPost-hook completed successfully
hook-post-failedPost-hook exited non-zero or timed out

Example

services:
  myapp:
    image: myapp:latest
    labels:
      - dd.watch=true
      - dd.hook.pre=docker exec mydb pg_dump -U postgres -F c -f /backup/pre-update.dump mydb
      - dd.hook.post=/usr/local/bin/notify-update.sh
      - dd.hook.pre.abort=true
      - dd.hook.timeout=120000

Commands that require shell redirects (>, |, etc.) must be wrapped in a script file and invoked via the hook (e.g. dd.hook.pre=/usr/local/bin/backup.sh).

Keep credentials out of hook labels. Put notification tokens and signed URLs in a mounted secret or environment variable read by the script instead.

Hooks run with the permissions of the drydock process. Ensure the Docker socket is mounted if your hooks need to interact with Docker.