Agents
Agent Mode allows running Drydock in a distributed manner across multiple Docker hosts.
The Agent Mode allows running drydock in a distributed manner.
- Agent Node: Runs near the Docker socket (or other container sources). It performs discovery and update checks.
- Controller Node: The central instance. It manages its own local watchers AND connects to remote Agents. It aggregates containers from Agents, and handles persistence, UI, and Notifications.
Architecture
The Controller connects to one or more Agents via HTTP/HTTPS. The Agent pushes real-time updates (container changes, new versions found) to the Controller using Server-Sent Events (SSE).
Agent Configuration
To run drydock in Agent mode, start the application with the --agent command line flag.
Agent Environment Variables
| Env var | Required | Description | Default |
|---|---|---|---|
DD_AGENT_SECRET | 🔴 | Secret token for authentication (must match Controller configuration) | |
DD_AGENT_SECRET_FILE | ⚪ | Path to file containing the secret token | |
DD_SERVER_PORT | ⚪ | Port to listen on | 3000 |
DD_SERVER_TLS_* | ⚪ | Standard Server TLS options | |
DD_WATCHER_{name}_* | 🔴 | Watcher configuration (At least one is required) | |
DD_ACTION_DOCKER_{name}_* | ⚪ | Docker trigger for container updates (required to update containers from UI/API) | |
DD_REGISTRY_{name}_* | ⚪ | Registry configuration (For update checks) |
DD_WATCHER_{name}_{option} (no provider segment — Docker is the only watcher type). Triggers are DD_ACTION_DOCKER_{name}_{option} (provider segment required). A common mistake is writing DD_ACTION_DOCKER_PRUNE=true (missing the name) — this creates a broken trigger named "prune" instead of a trigger with PRUNE=true. Use DD_ACTION_DOCKER_{name}_PRUNE=true instead.Agent Example (Docker Compose)
services:
drydock-socket-proxy:
image: lscr.io/linuxserver/socket-proxy:latest
container_name: drydock-socket-proxy
restart: unless-stopped
labels:
- dd.watch=true
- dd.update.mode=infrastructure
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- CONTAINERS=1
- IMAGES=1
- EVENTS=1
- INFO=1
- POST=1
- DELETE=1
- NETWORKS=1
- VOLUMES=1
healthcheck:
test: wget --spider http://localhost:2375/version || exit 1
interval: 5s
timeout: 3s
retries: 3
start_period: 5s
drydock-agent:
image: codeswhat/drydock
command: --agent
depends_on:
drydock-socket-proxy:
condition: service_healthy
ports:
- "3000:3000"
environment:
# Shared secret (must match controller's DD_AGENT_{name}_SECRET)
- DD_AGENT_SECRET=mysecretkey
# Watcher — discovers containers on this host
- DD_WATCHER_LOCAL_HOST=drydock-socket-proxy
- DD_WATCHER_LOCAL_PORT=2375
# Docker trigger — allows updating containers from UI/API
- DD_ACTION_DOCKER_LOCAL_PRUNE=trueController Configuration
To connect a Controller to an Agent, use the DD_AGENT_{name}_* environment variables.
Controller Environment Variables
| Env var | Required | Description | Default |
|---|---|---|---|
DD_AGENT_{name}_SECRET | 🔴 | Secret token to authenticate with the Agent | |
DD_AGENT_{name}_SECRET_FILE | ⚪ | Path to file containing the secret token | |
DD_AGENT_{name}_HOST | 🔴 | Hostname or IP of the Agent | |
DD_AGENT_{name}_PORT | ⚪ | Port of the Agent | 3000 |
DD_AGENT_{name}_CAFILE | ⚪ | CA certificate path for TLS connection | |
DD_AGENT_{name}_CERTFILE | ⚪ | Client certificate path for TLS connection | |
DD_AGENT_{name}_KEYFILE | ⚪ | Client key path for TLS connection | |
DD_AGENT_ALLOW_INSECURE_SECRET | ⚪ | Insecure. When true, allows configuring agents with a secret over plain HTTP. Drydock will log a warning on every startup but won't refuse to boot. Only enable on trusted private networks where you accept that the agent secret is sent in cleartext. | false |
DD_AGENT_ALLOW_INSECURE_SECRET=true means your agent secret is transmitted over the network in cleartext on every request. An attacker with network access can capture it and impersonate the controller. Prefer HTTPS (configure DD_AGENT_{name}_CERTFILE/DD_AGENT_{name}_CAFILE) or terminate TLS at a reverse proxy. Only use this flag on isolated private networks where you explicitly accept that risk.Controller Example (Docker Compose)
services:
drydock-controller:
image: codeswhat/drydock
environment:
- DD_AGENT_REMOTE1_HOST=192.168.1.50
- DD_AGENT_REMOTE1_SECRET=mysecretkey
ports:
- 3000:3000DD_LOCAL_WATCHER=false to disable the default local watcher.Complete Multi-Host Example
This example shows a controller watching its own containers and connecting to one remote agent. The controller handles notifications (Slack, SMTP, etc.); each agent handles its own container updates.
Controller
services:
drydock:
image: codeswhat/drydock
ports:
- "3000:3000"
depends_on:
socket-proxy:
condition: service_healthy
volumes:
- /path/to/store:/store
environment:
# --- Local watcher (controller's own containers) ---
- DD_WATCHER_LOCAL_HOST=drydock-socket-proxy
- DD_WATCHER_LOCAL_PORT=2375
- DD_WATCHER_LOCAL_WATCHBYDEFAULT=true
# --- Local docker trigger (update controller's own containers) ---
- DD_ACTION_DOCKER_LOCAL_PRUNE=true
# --- Remote agent connection ---
- DD_AGENT_REMOTE1_HOST=192.168.1.50
- DD_AGENT_REMOTE1_PORT=3000
- DD_AGENT_REMOTE1_SECRET=mysecretkey
# --- Notifications (run on controller only) ---
- DD_NOTIFICATION_SMTP_ALERTS_HOST=smtp.example.com
- DD_NOTIFICATION_SMTP_ALERTS_PORT=587
- DD_NOTIFICATION_SMTP_ALERTS_USER=user@example.com
- DD_NOTIFICATION_SMTP_ALERTS_PASS=password
- DD_NOTIFICATION_SMTP_ALERTS_FROM=drydock@example.com
- DD_NOTIFICATION_SMTP_ALERTS_TO=admin@example.com
socket-proxy:
image: lscr.io/linuxserver/socket-proxy:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- CONTAINERS=1
- IMAGES=1
- EVENTS=1
- INFO=1
- POST=1
- DELETE=1
- NETWORKS=1
- VOLUMES=1
healthcheck:
test: wget --spider http://localhost:2375/version || exit 1
interval: 5s
timeout: 3s
retries: 3
start_period: 5sAgent (on 192.168.1.50)
services:
drydock-socket-proxy:
image: lscr.io/linuxserver/socket-proxy:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- CONTAINERS=1
- IMAGES=1
- EVENTS=1
- INFO=1
- POST=1
- DELETE=1
- NETWORKS=1
- VOLUMES=1
healthcheck:
test: wget --spider http://localhost:2375/version || exit 1
interval: 5s
timeout: 3s
retries: 3
start_period: 5s
drydock-agent:
image: codeswhat/drydock
command: --agent
depends_on:
drydock-socket-proxy:
condition: service_healthy
ports:
- "3000:3000"
environment:
# Must match controller's DD_AGENT_REMOTE1_SECRET
- DD_AGENT_SECRET=mysecretkey
# Watcher — discovers containers on this host
- DD_WATCHER_LOCAL_HOST=drydock-socket-proxy
- DD_WATCHER_LOCAL_PORT=2375
# Docker trigger — required to update containers via UI/API
- DD_ACTION_DOCKER_LOCAL_PRUNE=truedocker and dockercompose triggers are supported in agent mode. Notification triggers must be configured on the controller.DD_ACTION_DOCKER_* on the controller for each agent. During the handshake, the controller automatically discovers the agent's triggers and creates internal proxies. The controller only needs its own DD_ACTION_DOCKER_* for updating containers on its local host.Reliability notes
Transient enumeration failures
When the Docker watcher on an agent fails to enumerate containers (e.g. a brief socket-proxy hiccup or Docker daemon restart), the agent suppresses the snapshot emission rather than broadcasting an empty container list. The controller preserves the last-known container state instead of wiping it. The next successful cron cycle restores the accurate state.
Container list payload
The agent's POST /update endpoint receives only { id, name } for each container rather than the full container object. This prevents HTTP 413 errors in large fleets where full payloads exceeded the agent's 256 KB body cap.
Features in Agent Mode
- Watchers: Run on the Agent to discover containers.
- Registries: Configured on the Agent to check for updates.
- Triggers:
dockeranddockercomposetriggers are configured and executed on the Agent (allowing update of remote containers). The controller automatically proxies update requests to the correct agent.- Notification triggers (e.g.
smtp,discord) are configured and executed on the Controller. Notifications automatically include a[server-name]prefix identifying which server each update comes from. The controller name defaults to the detected Docker or Podman daemon host name when available, then the process hostname, and can be overridden withDD_SERVER_NAME.