DrydockDrydock
ConfigurationWatchers

Docker Watchers

Watchers are responsible for scanning Docker containers.

logo

Watchers are responsible for scanning Docker containers.

The docker watcher lets you configure the Docker hosts you want to watch.

Variables

Env varRequiredDescriptionSupported valuesDefault value when missing
DD_WATCHER_{watcher_name}_CAFILECA pem file path (only for TLS connection)
DD_WATCHER_{watcher_name}_AUTH_BEARERBearer token for remote Docker API auth (HTTPS only)
DD_WATCHER_{watcher_name}_AUTH_INSECUREAllow fail-open remote auth fallback when auth is invalid or non-HTTPStrue, falsefalse
DD_WATCHER_{watcher_name}_AUTH_PASSWORDPassword for remote Docker API basic auth (HTTPS only)
DD_WATCHER_{watcher_name}_AUTH_TYPEAuth mode for remote Docker API authBASIC, BEARER, OIDCauto-detected from provided credentials
DD_WATCHER_{watcher_name}_AUTH_USERUsername for remote Docker API basic auth (HTTPS only)
DD_WATCHER_{watcher_name}_CERTFILECertificate pem file path (only for TLS connection)
DD_WATCHER_{watcher_name}_CRONScheduling optionsValid CRON expression0 * * * * (every hour)
DD_WATCHER_{watcher_name}_HOSTDocker hostname or ip of the host to watch
DD_WATCHER_{watcher_name}_JITTERJitter in ms applied to the CRON to better distribute the load on the registries (on the Hub at the first place)> 060000 (1 minute)
DD_WATCHER_{watcher_name}_KEYFILEKey pem file path (only for TLS connection)
DD_WATCHER_{watcher_name}_MAINTENANCE_WINDOWAllowed update schedule (checks outside this window are skipped)Valid CRON expression
DD_WATCHER_{watcher_name}_MAINTENANCE_WINDOW_TZTimezone used to evaluate MAINTENANCE_WINDOWIANA timezone (e.g. UTC, Europe/Paris)UTC
DD_WATCHER_{watcher_name}_PORTDocker port of the host to watch2375
DD_WATCHER_{watcher_name}_PROTOCOLDocker remote API protocolhttp, httpshttp
DD_WATCHER_{watcher_name}_SOCKETDocker socket to watchValid unix socket/var/run/docker.sock
DD_WATCHER_{watcher_name}_WATCHALLInclude containers in every state (created, paused, exited, restarting, etc.) — not just running onestrue, falsefalse
DD_WATCHER_{watcher_name}_WATCHATSTART (deprecated)If drydock must check for image updates during startuptrue, falsetrue if this watcher store is empty
DD_WATCHER_{watcher_name}_WATCHBYDEFAULTWatch containers that don't have an explicit dd.watch labeltrue, falsetrue
DD_WATCHER_{watcher_name}_WATCHEVENTSIf drydock must monitor docker eventstrue, falsetrue
DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_*Shared per-image defaults (image match + include/exclude/transform/link/display/trigger/lookup)See Image Set Presets section below
WATCHALL and WATCHBYDEFAULT are independent and operate at different stages. WATCHALL controls which container states Docker returns (running-only vs all states). WATCHBYDEFAULT controls whether unlabeled containers are watched — the dd.watch label always takes precedence when set. See the behavior matrix below for details.
DD_WATCHER_{watcher_name}_WATCHDIGEST is deprecated and will be removed in a future release. Use the dd.watch.digest container label instead.
If no watcher is configured, a default one named local will be automatically created (reading the Docker socket).
Multiple watchers can be configured (if you have multiple Docker hosts to watch). You just need to give them different names.
Socket configuration and host/port configuration are mutually exclusive.
If socket configuration is used, don't forget to mount the Docker socket on your drydock container.
If host/port configuration is used, don't forget to enable the Docker remote API. See dockerd documentation
If the Docker remote API is secured with TLS, don't forget to mount and configure the TLS certificates. See dockerd documentation
Remote watcher auth (AUTH_*) is fail-closed by default and requires HTTPS (PROTOCOL=https) or TLS certificate-based connections. Set AUTH_INSECURE=true only if you intentionally need legacy fail-open behavior.
Legacy compatibility: wud.* labels are still accepted as fallback to dd.*. When a fallback is used, drydock emits a one-time warning for that key and increments dd_legacy_input_total{source="label",key="<legacy_key>"}. To rewrite configs in place, run node dist/index.js config migrate --dry-run then node dist/index.js config migrate --file <path>.
If watcher logger initialization fails, drydock automatically falls back to structured stderr JSON logs and increments dd_watcher_logger_init_failures_total{type,name}. Alert on non-zero increases to catch degraded logging early.
When MAINTENANCE_WINDOW is configured and a check is skipped outside the allowed schedule, drydock queues one pending check and runs it automatically when the next maintenance window opens.
Watching image digests causes an extensive usage of Docker Registry Pull API which is restricted by Quotas on the Docker Hub. By default, drydock enables it only for non semver image tags. You can tune this behavior per container using the dd.watch.digest label. If you face quota related errors, consider slowing down the watcher rate by adjusting the DD_WATCHER_{watcher_name}_CRON variable.

Variable examples

Watch the local docker host every day at 1am

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
        - DD_WATCHER_LOCAL_CRON=0 1 * * *
docker run \
    -e DD_WATCHER_LOCAL_CRON="0 1 * * *" \
  ...
  codeswhat/drydock

Watch all containers regardless of their status (created, paused, exited, restarting, running...)

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
        - DD_WATCHER_LOCAL_WATCHALL=true
docker run \
    -e DD_WATCHER_LOCAL_WATCHALL="true" \
  ...
  codeswhat/drydock

WATCHALL / WATCHBYDEFAULT behavior matrix

These two variables are orthogonal — they filter at different stages of the container selection pipeline:

  1. WATCHALL (Docker API level) — decides which container states are fetched from Docker.
  2. WATCHBYDEFAULT (per-container level) — decides whether containers without a dd.watch label are watched. An explicit dd.watch label always takes precedence.
WATCHALLWATCHBYDEFAULTContainers fetchedUnlabeled containersdd.watch=truedd.watch=false
false (default)true (default)Running onlyWatchedWatchedNot watched
falsefalseRunning onlyNot watchedWatchedNot watched
truetrueAll statesWatchedWatchedNot watched
truefalseAll statesNot watchedWatchedNot watched
A stopped container with dd.watch=true will not be watched when WATCHALL is false because it is never returned by the Docker API in the first place. Set WATCHALL=true if you need to monitor non-running containers.

Watch a remote docker host via TCP on 2375

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
        - DD_WATCHER_MYREMOTEHOST_HOST=myremotehost
docker run \
    -e DD_WATCHER_MYREMOTEHOST_HOST="myremotehost" \
  ...
  codeswhat/drydock

Watch a remote docker host behind HTTPS with bearer auth

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
        - DD_WATCHER_MYREMOTEHOST_HOST=myremotehost
        - DD_WATCHER_MYREMOTEHOST_PORT=443
        - DD_WATCHER_MYREMOTEHOST_PROTOCOL=https
        - DD_WATCHER_MYREMOTEHOST_AUTH_TYPE=BEARER
        - DD_WATCHER_MYREMOTEHOST_AUTH_BEARER=my-secret-token
docker run \
    -e DD_WATCHER_MYREMOTEHOST_HOST="myremotehost" \
    -e DD_WATCHER_MYREMOTEHOST_PORT="443" \
    -e DD_WATCHER_MYREMOTEHOST_PROTOCOL="https" \
    -e DD_WATCHER_MYREMOTEHOST_AUTH_TYPE="BEARER" \
    -e DD_WATCHER_MYREMOTEHOST_AUTH_BEARER="my-secret-token" \
  ...
  codeswhat/drydock

Watch a remote docker host via TCP with TLS enabled on 2376

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
        - DD_WATCHER_MYREMOTEHOST_HOST=myremotehost
        - DD_WATCHER_MYREMOTEHOST_PORT=2376
        - DD_WATCHER_MYREMOTEHOST_CAFILE=/certs/ca.pem
        - DD_WATCHER_MYREMOTEHOST_CERTFILE=/certs/cert.pem
        - DD_WATCHER_MYREMOTEHOST_KEYFILE=/certs/key.pem
    volumes:
        - /my-host/my-certs/ca.pem:/certs/ca.pem:ro
        - /my-host/my-certs/ca.pem:/certs/cert.pem:ro
        - /my-host/my-certs/ca.pem:/certs/key.pem:ro
docker run \
    -e DD_WATCHER_MYREMOTEHOST_HOST="myremotehost" \
    -e DD_WATCHER_MYREMOTEHOST_PORT="2376" \
    -e DD_WATCHER_MYREMOTEHOST_CAFILE="/certs/ca.pem" \
    -e DD_WATCHER_MYREMOTEHOST_CERTFILE="/certs/cert.pem" \
    -e DD_WATCHER_MYREMOTEHOST_KEYFILE="/certs/key.pem" \
    -v /my-host/my-certs/ca.pem:/certs/ca.pem:ro \
    -v /my-host/my-certs/ca.pem:/certs/cert.pem:ro \
    -v /my-host/my-certs/ca.pem:/certs/key.pem:ro \
  ...
  codeswhat/drydock
Don't forget to mount the certificates into the container!

Docker Socket Security

Drydock needs to communicate with the Docker Engine API to monitor containers and (optionally) perform updates. By default this means mounting the Docker socket — which grants broad access to the host. This section covers all available approaches to secure that access, from the recommended socket proxy to remote TLS connections.

Security comparison

ApproachAttack surfacePrivilege levelSetup complexityAuto-updates?
Socket proxy (recommended)Filtered API onlyNon-rootLowYes (with POST=1)
Remote Docker over TLSNetwork + TLSNon-rootMediumYes
Rootless DockerFull API, unprivileged daemonNon-rootMediumYes
Direct socket mountFull Docker APIRootTrivialYes
Break-glass root modeFull Docker API + host rootRootTrivialYes

A socket proxy runs as a separate container with access to the Docker socket and exposes only the API endpoints Drydock needs. Drydock connects to the proxy over HTTP, so no socket mount is required at all.

This is the recommended approach for all deployments. It provides a strict security boundary with minimal setup.

services:
  drydock:
    image: codeswhat/drydock
    depends_on:
      socket-proxy:
        condition: service_healthy
    environment:
      - DD_WATCHER_LOCAL_HOST=socket-proxy
      - DD_WATCHER_LOCAL_PORT=2375
    ports:
      - 3000:3000

  socket-proxy:
    image: tecnativa/docker-socket-proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - CONTAINERS=1
      - IMAGES=1
      - EVENTS=1
      - SERVICES=1
    healthcheck:
      test: wget --spider http://localhost:2375/version || exit 1
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 5s
    restart: unless-stopped
The :ro (read-only) flag on the Docker socket mount is omitted intentionally. The proxy's environment variables (CONTAINERS, EVENTS, etc.) control which API endpoints are exposed — that is the actual security boundary. The :ro flag on Unix sockets does not prevent API communication and can cause connection failures on some Linux kernels.

Proxy permissions by feature

FeatureRequired proxy env vars
Watch containers (default)CONTAINERS=1, IMAGES=1, EVENTS=1, SERVICES=1
Container actions (start/stop/restart)All of the above plus POST=1 and a Docker trigger
Docker trigger (auto-updates)All of the above plus POST=1, NETWORKS=1
Container actions (start, stop, restart, update) require a Docker trigger to be configured. If you only want container actions without automatic updates, set DD_TRIGGER_DOCKER_{name}_AUTO=false. Without a Docker trigger, the UI will show "No docker trigger found for this container" when attempting actions.

Alternative socket proxies

Tecnativa/docker-socket-proxy is the most widely used option, but any HAProxy or nginx-based Docker socket proxy that filters by HTTP method and path will work. The key requirement is that the proxy exposes the Docker Engine API endpoints listed in the permissions table above and blocks everything else (especially exec, build, and swarm endpoints).

Other compatible proxies include linuxserver/docker-socket-proxy (a fork with additional features).

Option 2: Remote Docker over TLS

Instead of mounting the socket at all, Drydock can connect to a remote Docker host over the network using mutual TLS (mTLS). This is ideal for multi-host setups or when you want to completely avoid socket mounts.

Step 1: Generate TLS certificates on the Docker host

Follow the official Docker TLS guide to generate a CA, server certificate, and client certificate.

Step 2: Configure the Docker daemon

Configure the daemon to listen on a TLS port by adding this to /etc/docker/daemon.json:

{
  "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"],
  "tls": true,
  "tlscacert": "/etc/docker/ca.pem",
  "tlscert": "/etc/docker/server-cert.pem",
  "tlskey": "/etc/docker/server-key.pem",
  "tlsverify": true
}

Step 3: Configure Drydock

Configure Drydock with the client certificates:

services:
  drydock:
    image: codeswhat/drydock
    environment:
      - DD_WATCHER_REMOTE_HOST=docker-host.example.com
      - DD_WATCHER_REMOTE_PORT=2376
      - DD_WATCHER_REMOTE_CAFILE=/certs/ca.pem
      - DD_WATCHER_REMOTE_CERTFILE=/certs/client-cert.pem
      - DD_WATCHER_REMOTE_KEYFILE=/certs/client-key.pem
    volumes:
      - ./certs/ca.pem:/certs/ca.pem:ro
      - ./certs/client-cert.pem:/certs/client-cert.pem:ro
      - ./certs/client-key.pem:/certs/client-key.pem:ro
    ports:
      - 3000:3000
When TLS certificates are provided (CAFILE, CERTFILE, KEYFILE), Drydock automatically uses HTTPS regardless of the PROTOCOL setting.
Keep your client certificates secure. Mount them read-only (:ro) and restrict file permissions to the Drydock user.

Option 3: Rootless Docker

Rootless Docker runs the Docker daemon entirely in user space without root privileges. Even full socket access does not grant host root because the daemon itself is unprivileged.

Step 1: Install rootless Docker following the official guide.

Step 2: Find your rootless socket path:

echo $XDG_RUNTIME_DIR/docker.sock
# Typically: /run/user/1000/docker.sock

Step 3: Mount the rootless socket:

services:
  drydock:
    image: codeswhat/drydock
    volumes:
      - /run/user/1000/docker.sock:/var/run/docker.sock
    ports:
      - 3000:3000
The rootless socket path varies by user. Replace 1000 with your user's UID (id -u).
Rootless Docker has some limitations: no --privileged containers, limited network configuration, and some storage drivers may not be available. Check the official limitations list for your use case.

You can combine rootless Docker with a socket proxy for defense in depth — even if the proxy is compromised, the attacker only gains unprivileged access.

Option 4: Direct socket mount (default)

The simplest approach — mount the Docker socket directly. Drydock runs as a non-root user inside the container, but the socket grants access to the full Docker API.

services:
  drydock:
    image: codeswhat/drydock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - 3000:3000
Direct socket access grants the container full control over the Docker daemon, including the ability to create privileged containers, mount host filesystems, and effectively gain root access to the host. Use a socket proxy or remote TLS connection for production deployments.

Option 5: Break-glass root mode (least secure)

If the non-root user cannot connect to the socket (common with :ro mounts), you can explicitly opt into root mode. This is a break-glass path and should not be your default. Both flags are required; setting only DD_RUN_AS_ROOT=true fails closed at startup.

services:
  drydock:
    image: codeswhat/drydock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      - DD_RUN_AS_ROOT=true
      - DD_ALLOW_INSECURE_ROOT=true
    ports:
      - 3000:3000
Running as root trades away the privilege-drop security boundary. Use this only as a temporary break-glass fallback; prefer socket proxy mode for long-term deployments.

Which Docker API endpoints does Drydock use?

Understanding exactly what Drydock accesses helps you configure socket proxies and evaluate your security posture.

Read-only operations (watchers):

EndpointPurpose
GET /containers/jsonList running containers
GET /containers/{id}/jsonInspect container details and labels
GET /images/jsonList images on the host
GET /images/{id}/jsonInspect image metadata (tags, digests, architecture)
GET /eventsStream real-time container lifecycle events
GET /services/{id}Inspect Docker Swarm service labels

Write operations (triggers — only when performing updates):

EndpointPurpose
POST /images/createPull new image versions
POST /containers/createCreate replacement container
POST /containers/{id}/startStart the new container
POST /containers/{id}/stopStop the old container
POST /containers/{id}/waitWait for container removal
DELETE /containers/{id}Remove the old container
DELETE /images/{id}Prune old images (when enabled)
POST /networks/{id}/connectConnect container to additional networks

If you only need monitoring (no auto-updates), a read-only socket proxy configuration is sufficient.

Watch 1 local Docker host and 2 remote docker hosts at the same time

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
        -  DD_WATCHER_LOCAL_SOCKET=/var/run/docker.sock
        -  DD_WATCHER_MYREMOTEHOST1_HOST=myremotehost1
        -  DD_WATCHER_MYREMOTEHOST2_HOST=myremotehost2
docker run \
    -e  DD_WATCHER_LOCAL_SOCKET="/var/run/docker.sock" \
    -e  DD_WATCHER_MYREMOTEHOST1_HOST="myremotehost1" \
    -e  DD_WATCHER_MYREMOTEHOST2_HOST="myremotehost2" \
  ...
  codeswhat/drydock

Maintenance window

Only allow update checks between 2 AM and 4 AM in Europe/Berlin:

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
      - DD_WATCHER_LOCAL_CRON=0 * * * *
      - DD_WATCHER_LOCAL_MAINTENANCE_WINDOW=0 2-3 * * *
      - DD_WATCHER_LOCAL_MAINTENANCE_WINDOW_TZ=Europe/Berlin
docker run \
    -e DD_WATCHER_LOCAL_CRON="0 * * * *" \
    -e DD_WATCHER_LOCAL_MAINTENANCE_WINDOW="0 2-3 * * *" \
    -e DD_WATCHER_LOCAL_MAINTENANCE_WINDOW_TZ="Europe/Berlin" \
  ...
  codeswhat/drydock

Image Set Presets

Use IMGSET to define reusable defaults by image reference. This is useful when many containers need the same tag filters, link template, icon, or trigger routing.

Looking for ready-to-copy presets for common containers? See Popular IMGSET Presets.

Supported imgset keys

  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_IMAGE
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_TAG_INCLUDE
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_TAG_EXCLUDE
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_TAG_TRANSFORM
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_LINK_TEMPLATE
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_DISPLAY_NAME
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_DISPLAY_ICON
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_TRIGGER_INCLUDE
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_TAG_FAMILY
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_WATCH_DIGEST
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_INSPECT_TAG_PATH
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_TRIGGER_EXCLUDE
  • DD_WATCHER_{watcher_name}_IMGSET_{imgset_name}_REGISTRY_LOOKUP_IMAGE

Imgset precedence

  • dd.* labels on the container (or swarm service/container merged labels) are highest priority.
  • IMGSET values are defaults applied only when the corresponding label is not set.

Imgset example

services:
  drydock:
    image: codeswhat/drydock
    environment:
      - DD_WATCHER_LOCAL_IMGSET_HOMEASSISTANT_IMAGE=ghcr.io/home-assistant/home-assistant
      - DD_WATCHER_LOCAL_IMGSET_HOMEASSISTANT_TAG_INCLUDE=^\\d+\\.\\d+\\.\\d+$$
      - DD_WATCHER_LOCAL_IMGSET_HOMEASSISTANT_DISPLAY_NAME=Home Assistant
      - DD_WATCHER_LOCAL_IMGSET_HOMEASSISTANT_DISPLAY_ICON=hl-home-assistant
      - DD_WATCHER_LOCAL_IMGSET_HOMEASSISTANT_LINK_TEMPLATE=https://www.home-assistant.io/changelogs/core-$${major}$${minor}$${patch}
      - DD_WATCHER_LOCAL_IMGSET_HOMEASSISTANT_TRIGGER_INCLUDE=ntfy.default:major

Labels

To fine-tune the behaviour of drydock per container, you can add labels on them.

LabelRequiredDescriptionSupported valuesDefault value when missing
dd.display.iconCustom display icon for the containerValid Fontawesome Icon, Homarr Labs Icon, Selfh.st Icon, or Simple Icon (see details below). mdi: icons are auto-resolved but not recommended.fab fa-docker
dd.display.pictureCustom entity picture URL for Home Assistant MQTT integration. When set to an HTTP/HTTPS URL, overrides the icon-derived entity_picture in HASS discovery payloads.Valid HTTP or HTTPS URL
dd.display.nameCustom display name for the containerValid StringContainer name
dd.groupGroup name for stack/group views in the UI (falls back to com.docker.compose.project if not set)Valid String
dd.inspect.tag.pathDocker inspect path used to derive a local semver tagSlash-separated path in docker inspect output
dd.registry.lookup.imageAlternative image reference used for update lookupsFull image path (for example library/traefik or ghcr.io/traefik/traefik)
dd.link.templateBrowsable link associated to the container versionJS string template with vars ${container}, ${original}, ${transformed}, ${major}, ${minor}, ${patch}, ${prerelease}
dd.tag.excludeRegex to exclude specific tagsValid JavaScript Regex
dd.tag.includeRegex to include specific tags onlyValid JavaScript Regex
dd.tag.transformTransform function to apply to the tag$valid_regex => $valid_string_with_placeholders (see below)
dd.tag.familyTag family policy for semver updatesstrict (default) or loosestrict
dd.trigger.excludeOptional list of triggers to exclude$trigger_1_id_or_name,$trigger_2_id_or_name:$threshold
dd.trigger.includeOptional list of triggers to include$trigger_1_id_or_name,$trigger_2_id_or_name:$threshold
dd.watch.digestWatch this container digestValid Booleanfalse
dd.watchExplicitly include or exclude this container from monitoring (overrides WATCHBYDEFAULT)Valid BooleanInherits from WATCHBYDEFAULT (true by default)
dd.compose.filePath to the docker-compose.yml file for compose-native updates (also configurable via trigger COMPOSEFILELABEL)file path
dd.webhook.enabledAllow or block webhook API calls targeting this containertrue, falsetrue
dd.updatePolicy.maturityModeUpdate maturity policy — all allows any update, mature blocks updates detected less than maturityMinAgeDays agoall, matureall
dd.updatePolicy.maturityMinAgeDaysMinimum age in days before an update is considered mature (only applies when maturityMode is mature)integer (>=1)7
dd.runtime.entrypoint.originTracks whether the container's entrypoint was explicitly set or inherited from the image. Used during updates to decide whether to preserve the current entrypoint or let the new image defaults apply.explicit, inherited, unknownauto-detected
dd.runtime.cmd.originTracks whether the container's cmd was explicitly set or inherited from the image. Used during updates to decide whether to preserve the current cmd or let the new image defaults apply.explicit, inherited, unknownauto-detected
dd.runtime.entrypoint.origin and dd.runtime.cmd.origin are managed automatically by the Docker trigger during container updates. You only need to set them manually if drydock cannot detect the origin (shows unknown) and you want to explicitly pin or release a custom entrypoint/cmd.
dd.inspect.tag.path is optional and opt-in. Use it only when your image metadata tracks the running app version reliably; some images set unrelated values.
Legacy alias dd.registry.lookup.url is still accepted for compatibility, but prefer dd.registry.lookup.image.

Label examples

Include specific containers to watch

Set WATCHBYDEFAULT=false so that only containers with an explicit dd.watch=true label are monitored.

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
      - DD_WATCHER_LOCAL_WATCHBYDEFAULT=false
docker run \
    -e DD_WATCHER_LOCAL_WATCHBYDEFAULT="false" \
  ...
  codeswhat/drydock

Then add the dd.watch=true label on the containers you want to watch.

services:
  mariadb:
    image: mariadb:10.4.5
    ...
    labels:
      - dd.watch=true
docker run -d --name mariadb --label dd.watch=true mariadb:10.4.5

Exclude specific containers to watch

Ensure DD_WATCHER_{watcher_name}_WATCHBYDEFAULT is true (default value).

Then add the dd.watch=false label on the containers you want to exclude from being watched.

services:
  mariadb:
    image: mariadb:10.4.5
    ...
    labels:
      - dd.watch=false
docker run -d --name mariadb --label dd.watch=false mariadb:10.4.5

Derive a semver from Docker inspect when image tag is latest

Use this when the running container exposes a version label in docker inspect.

services:
  myapp:
    image: ghcr.io/example/myapp:latest
    labels:
      - dd.inspect.tag.path=Config/Labels/org.opencontainers.image.version
docker run -d \
  --name myapp \
  --label dd.inspect.tag.path=Config/Labels/org.opencontainers.image.version \
  ghcr.io/example/myapp:latest

Use an alternative image for update lookups

Use this when your runtime image is pulled from a cache/proxy registry, but you want updates checked against an upstream image.

services:
  traefik:
    image: harbor.example.com/dockerhub-proxy/traefik:v3.5.3
    labels:
      - dd.watch=true
      - dd.registry.lookup.image=library/traefik
docker run -d \
  --name traefik \
  --label 'dd.watch=true' \
  --label 'dd.registry.lookup.image=library/traefik' \
  harbor.example.com/dockerhub-proxy/traefik:v3.5.3

Include only 3 digits semver tags

You can filter (by inclusion or exclusion) which versions can be candidates for update.

For example, you can indicate that you want to watch x.y.z versions only

services:

  mariadb:
    image: mariadb:10.4.5
    labels:
      - dd.tag.include=^\d+\.\d+\.\d+$$
docker run -d --name mariadb --label 'dd.tag.include=^\d+\.\d+\.\d+$' mariadb:10.4.5

Transform the tags before performing the analysis

In certain cases, tag values are so badly formatted that the resolution algorithm cannot find any valid update candidates or, worst, find bad positive matches.

For example, you can encounter such an issue if you need to deal with tags looking like 1.0.0-99-7b368146, 1.0.0-273-21d7efa6...
By default, drydock will report bad positive matches because of the sha-1 part at the end of the tag value (-7b368146...).
That's a shame because 1.0.0-99 and 1.0.0-273 would have been valid semver values ($major.$minor.$patch-$prerelease).

You can get around this issue by providing a function that keeps only the part you are interested in.

How does it work?
The transform function must follow the following syntax:

$valid_regex_with_capturing_groups => $valid_string_with_placeholders

For example:

^(\d+\.\d+\.\d+-\d+)-.*$ => $1

The capturing groups are accessible with the syntax $1, $2, $3....

The first capturing group is accessible as $1!

For example, you can indicate that you want to watch x.y.z versions only

services:

  searx:
    image: searx/searx:1.0.0-269-7b368146
    labels:
      - dd.tag.include=^\d+\.\d+\.\d+-\d+-.*$$
      - dd.tag.transform=^(\d+\.\d+\.\d+-\d+)-.*$$ => $$1
docker run -d --name searx \
--label 'dd.tag.include=^\d+\.\d+\.\d+-\d+-.*$' \
--label 'dd.tag.transform=^(\d+\.\d+\.\d+-\d+)-.*$ => $1' \
searx/searx:1.0.0-269-7b368146

Enable digest watching

Additionally to semver tag tracking, you can also track if the digest associated to the local tag has been updated.
It can be convenient to monitor image tags known to be overridden (latest, 10, 10.6...)

services:

  mariadb:
    image: mariadb:10
    labels:
      - dd.tag.include=^\d+$$
      - dd.watch.digest=true
docker run -d --name mariadb --label 'dd.tag.include=^\d+$' --label dd.watch.digest=true mariadb:10

You can associate a browsable link to the container version using a templated string. For example, if you want to associate a mariadb version to a changelog (e.g. https://mariadb.com/kb/en/mariadb-1064-changelog),

you would specify a template like https://mariadb.com/kb/en/mariadb-${major}${minor}${patch}-changelog

The available variables are:

  • ${original} the original unparsed tag
  • ${transformed} the original unparsed tag transformed with the optional dd.tag.transform label option
  • ${major} the major version (if tag value is semver)
  • ${minor} the minor version (if tag value is semver)
  • ${patch} the patch version (if tag value is semver)
  • ${prerelease} the prerelease version (if tag value is semver)

Customize the name and the icon to display

You can customize the name & the icon of a container (displayed in the UI, in Home-Assistant...)

Icons must be prefixed with:

If you want to display Fontawesome icons or Simple icons in Home-Assistant, you need to install first the HASS-fontawesome and the HASS-simpleicons components.
services:

  mariadb:
    image: mariadb:10.6.4
    labels:
      - dd.display.name=Maria DB
      - dd.display.icon=si:mariadb
docker run -d --name mariadb --label 'dd.display.name=Maria DB' --label 'dd.display.icon=si:mariadb' mariadb:10.6.4

Assign different triggers to containers

You can assign different triggers and thresholds on a per container basis.

Example send a mail notification for all updates but auto-update only if minor or patch

services:

  my_important_service:
    image: my_important_service:1.0.0
    labels:
      - dd.trigger.include=smtp.gmail,dockercompose.local:minor
docker run -d --name my_important_service --label 'dd.trigger.include=smtp.gmail,dockercompose.local:minor' my_important_service:1.0.0
dd.trigger.include=smtp.gmail is a shorthand for dd.trigger.include=smtp.gmail:all
dd.trigger.include=update (or dd.trigger.exclude=update) targets all triggers named update, for example docker.update and discord.update
Threshold all means that the trigger will run regardless of the nature of the change
Threshold major means that the trigger will run only if this is a major, minor or patch semver change
Threshold minor means that the trigger will run only if this is a minor or patch semver change
Threshold patch means that the trigger will run only if this is a patch semver change
Threshold digest means that the trigger will run only on digest updates
Any threshold ending with -no-digest excludes digest updates for that threshold

Container Runtime Details

Each monitored container exposes runtime details sourced from Docker inspect and the container summary. These are visible in the API response and the UI:

FieldTypeDescription
details.portsstring[]Published port mappings (e.g. 8080->80/tcp, 443/tcp)
details.volumesstring[]Volume and bind mounts (e.g. myvolume:/data, /host/path:/container/path:ro)
details.env{ key, value }[]Environment variables set on the container

Container Status Fields

The container model includes additional fields useful for understanding update state:

FieldTypeDescription
result.noUpdateReasonstringWhen no update is available, explains why (e.g. tag filtering, no newer version found)
updateDetectedAtstring (ISO 8601)Timestamp of when an update was first detected for this container

Docker Event Stream

The watcher monitors Docker events (container create, destroy, start, stop, etc.) in real time. If the event stream disconnects, drydock automatically reconnects with exponential backoff starting at 1 second and capping at 30 seconds.

On this page

VariablesVariable examplesWatch the local docker host every day at 1amWatch all containers regardless of their status (created, paused, exited, restarting, running...)WATCHALL / WATCHBYDEFAULT behavior matrixWatch a remote docker host via TCP on 2375Watch a remote docker host behind HTTPS with bearer authWatch a remote docker host via TCP with TLS enabled on 2376Docker Socket SecuritySecurity comparisonOption 1: Socket proxy (recommended)Proxy permissions by featureAlternative socket proxiesOption 2: Remote Docker over TLSStep 1: Generate TLS certificates on the Docker hostStep 2: Configure the Docker daemonStep 3: Configure DrydockOption 3: Rootless DockerOption 4: Direct socket mount (default)Option 5: Break-glass root mode (least secure)Which Docker API endpoints does Drydock use?Watch 1 local Docker host and 2 remote docker hosts at the same timeMaintenance windowImage Set PresetsSupported imgset keysImgset precedenceImgset exampleLabelsLabel examplesInclude specific containers to watchExclude specific containers to watchDerive a semver from Docker inspect when image tag is latestUse an alternative image for update lookupsInclude only 3 digits semver tagsTransform the tags before performing the analysisEnable digest watchingAssociate a link to the container versionCustomize the name and the icon to displayAssign different triggers to containersExample send a mail notification for all updates but auto-update only if minor or patchContainer Runtime DetailsContainer Status FieldsDocker Event Stream