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_PASSWORDPassword for remote Docker API basic auth (HTTPS only)
DD_WATCHER_{watcher_name}_AUTH_TYPEAuth mode for remote Docker API authBASIC, BEARERauto-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}_WATCHALLIf drydock must monitor all containers instead of 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}_WATCHBYDEFAULTIf drydock must monitor all containers by defaulttrue, 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
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 only applied on HTTPS connections (PROTOCOL=https) or TLS certificate-based connections.
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

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 runs as a non-root user by default for security. This means a read-only Docker socket mount (:ro) will fail with a permission error because the Linux VFS denies connect() on a read-only bind mount for non-root users.

There are two ways to resolve this:

Option 1: DD_RUN_AS_ROOT=true (quick fix)

Set the DD_RUN_AS_ROOT environment variable to skip Drydock's privilege drop and run as root — the same approach used by Portainer, Watchtower, and other Docker management tools.

services:
  drydock:
    image: codeswhat/drydock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      - DD_RUN_AS_ROOT=true
    ports:
      - 3000:3000
Running as root trades the privilege-drop security boundary for :ro socket compatibility. This is safe for most home-lab and single-host setups but may not meet your security requirements in multi-tenant or production environments.

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.

services:
  drydock:
    image: codeswhat/drydock
    depends_on:
      - socket-proxy
    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:ro
    environment:
      - CONTAINERS=1
      - IMAGES=1
      - EVENTS=1
      - SERVICES=1
    restart: unless-stopped

Proxy permissions by feature

FeatureRequired proxy env vars
Watch containers (default)CONTAINERS=1, IMAGES=1, EVENTS=1, SERVICES=1
Docker trigger (auto-updates)All of the above plus POST=1, NETWORKS=1

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

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}_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.nameCustom display name for the containerValid StringContainer name
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.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.watchWatch this containerValid Booleantrue when DD_WATCHER_{watcher_name}_WATCHBYDEFAULT is true (false otherwise)
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

Configure drydock to disable WATCHBYDEFAULT feature.

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

On this page