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
| Label | Type | Default | Description |
|---|---|---|---|
dd.hook.pre | string | — | Shell command to execute before the update |
dd.hook.post | string | — | Shell command to execute after the update |
dd.hook.pre.abort | boolean | true | Abort the update if the pre-hook exits with a non-zero code |
dd.hook.timeout | number (ms) | 60000 | Maximum execution time for each hook |
Legacy
wud.hook.* labels are still supported for backward compatibility. When used, drydock logs a one-time deprecation warning and records the fallback in dd_legacy_input_total. You can rewrite labels with node dist/index.js config migrate.Hook enablement
| Env var | Required | Description | Supported values | Default |
|---|---|---|---|---|
DD_HOOKS_ENABLED | ⚪ | Enable execution of lifecycle hook labels (dd.hook.* / wud.hook.*) | true, false | false |
Hooks sourced from labels are skipped by default. Set
DD_HOOKS_ENABLED=true to opt in.Hook environment variables
When a hook runs, drydock injects environment variables describing the update:
| Variable | Description | Example |
|---|---|---|
DD_CONTAINER_NAME | Container name | myapp |
DD_CONTAINER_ID | Container ID | abc123def456 |
DD_IMAGE_NAME | Image name (without registry prefix) | myorg/myapp |
DD_IMAGE_TAG | Current image tag | 1.2.3 |
DD_UPDATE_KIND | Update type | tag or digest |
DD_UPDATE_FROM | Current tag or digest | 1.2.3 |
DD_UPDATE_TO | New tag or digest | 1.3.0 |
Execution details
- Hooks run inside the drydock container using
/bin/sh -conly whenDD_HOOKS_ENABLED=true - Combined stdout and stderr output is capped at 10 KB
- An exit code of
0is 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.*/wud.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.sockonly when required. - Mitigation: Keep
dd.hook.pre.abort=truewhen 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
| Event | Description |
|---|---|
hook-configured | Hook labels were detected for the update lifecycle |
hook-pre-success | Pre-hook completed successfully |
hook-pre-failed | Pre-hook exited non-zero or timed out |
hook-post-success | Post-hook completed successfully |
hook-post-failed | Post-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 mydb > /backup/pre-update.sql
- dd.hook.post=curl -X POST https://hooks.slack.com/services/xxx -d '{"text":"myapp updated"}'
- dd.hook.pre.abort=true
- dd.hook.timeout=120000Hooks run with the permissions of the drydock process. Ensure the Docker socket is mounted if your hooks need to interact with Docker.