A lightweight plugin that lets ioBroker define, start, update, monitor, and optionally back up Docker containers declared via Docker Compose files. It translates adapter configuration values into container settings and keeps containers in sync with your instance configuration.
- Define one or more Docker Compose files per ioBroker instance
- Dynamic variable substitution from adapter config (e.g.
${config.dockerInflux.enabled}) - Automatic (re)creation of containers when configuration changes
- Optional image auto-update & basic health monitoring
- Graceful stop on instance unload (configurable per container)
- Backup integration for declared data volumes
- Pre‑start file provisioning (copy local dirs into named volumes)
- Unified network & naming scheme to avoid collisions
Use it if you want to:
- Ship recommended Dockerized services together with an ioBroker adapter (e.g. InfluxDB + Grafana)
- Ensure containers stay aligned with the instance configuration
- Automate startup, teardown, and updates without writing custom scripts
- Node.js 20+
- Docker Engine 20.10+
Add the following to the common.plugins section:
{
// ...
"common": {
// ...
"plugins": {
"docker": {
"iobDockerComposeFiles": ["docker-compose.yaml"],
"iobDockerApi": "default", // optional
}
}
// ...
}
// ...
}Field notes:
iobDockerApi(optional): Name of a Docker connection defined undersystem.docker => native. You may also use a pattern like${config.dockerApiName}to reference adapter config.iobDockerComposeFiles: Array of relative paths (from the adapter root directory) to Docker Compose files.
Paths listed in iobDockerComposeFiles are resolved relative to the adapter's installation directory inside ioBroker. A typical docker-compose.yaml:
version: '3.9'
services:
influx:
# If container_name is omitted, a default name is used: iob_<adapterName>_<instance>
image: influxdb:2
labels:
# ioBroker-specific control (see a section below)
- 'iobEnabled=${config.dockerInflux.enabled:-true}'
- 'iobStopOnUnload=${config.dockerInflux.stopIfInstanceStopped:-true}'
- 'iobBackup=flux_data'
container_name: influx
ports:
- '${config.dockerInflux.bind:-127.0.0.1}:${config_dockerInflux_port:-8086}:8086'
environment:
DOCKER_INFLUXDB_INIT_MODE: 'setup'
DOCKER_INFLUXDB_INIT_USERNAME: 'iobroker'
DOCKER_INFLUXDB_INIT_PASSWORD: 'iobroker'
DOCKER_INFLUXDB_INIT_BUCKET: 'iobroker'
DOCKER_INFLUXDB_INIT_ORG: 'iobroker'
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: 'aW9icm9rZXI4NjY0NTYzODU0NjU2NTY1MjY1Ng=='
volumes:
- flux_data:/var/lib/influxdb2 # All volume names will be prefixed with `iob_<adapterName>_<instance>_`
- flux_config:/etc/influxdb2
networks:
- true # Use the default shared network name: `iob_<adapterName>_<instance>`. Otherwise, the name is prefixed with `iob_<adapterName>_<instance>_`. The only exception is network `iobroker`, which is used as-is.
restart: unless-stopped
grafana:
image: grafana/grafana-oss
labels:
- 'iobEnabled=${config.dockerInflux.enabled:-true}'
- 'iobStopOnUnload=${config.dockerInflux.stopIfInstanceStopped:-true}'
- 'iobCopyVolumes=./grafana-provisioning=>grafana_provisioning'
- 'iobWaitForReady=true'
container_name: grafana
depends_on:
- influx
ports:
# Use underscore variant for nested config paths that otherwise confuse validation
- '${config.dockerGrafana.bind:-127.0.0.1}:${config_dockerGrafana_port:-3000}:3000'
environment:
GF_SECURITY_ADMIN_PASSWORD: '${config.dockerGrafana.adminSecurityPassword:-iobroker}'
GF_SERVER_ROOT_URL: '${config.dockerGrafana.serverRootUrl:-}'
GF_INSTALL_PLUGINS: '${config.dockerGrafana.plugins:-}'
GF_USERS_ALLOW_SIGN_UP: '${config.dockerGrafana.usersAllowSignUp:-false}'
volumes:
- grafana_data:/var/lib/grafana # All volume names will be prefixed with `iob_<adapterName>_<instance>_`
- grafana_provisioning:/etc/grafana/provisioning
networks:
- true # Use the default shared network name: `iob_<adapterName>_<instance>`. Otherwise, the name is prefixed with `iob_<adapterName>_<instance>_`. The only exception is network `iobroker`, which is used as-is.
restart: unless-stopped
networks:
true: # Literal "true" selects the standardized network name `iob_<adapterName>_<instance>`
driver: bridge
volumes:
flux_data:
flux_config:
grafana_data:
grafana_provisioning:Add these labels under each service to control behavior:
iobEnabled(required, no default) Managing a service is an explicit opt-in: it is only created and started if this label is set totrue. A service without the label — or withfalse— is left untouched, so a Compose file can contain services that the plugin must not manage. Typically wired to an adapter setting:iobEnabled=${config.dockerInflux.enabled:-true}.iobStopOnUnload(default:false) Set it totrueto stop the container when the instance stops or is unloaded. LikeiobEnabled, this is an opt-in: without the label — or withfalse— the container keeps running, so a service that other instances use as well is not torn down behind their back. See Stopping containers on unload if your container needs longer than a second to stop.iobBackupComma‑separated list of named volumes to include in ioBroker backups.iobAutoImageUpdate(default:false) Iftrue, the plugin pulls and recreates the container when a newer image is available.iobMonitoringEnabled(default:false) Basic status monitoring; restarts container if it exits unexpectedly.iobWaitForReady(default:false) Delay container start until the adapter signals readiness (useful for generated config files).iobCopyVolumesCopy (one time or when changed) local directories into named volumes. Format:relative/path=>docker_volume[,another=>vol2].
You can inject adapter config values into Compose using ${config.<path>}. For some deeply nested keys or those containing dots, a transformed alias like config_dockerGrafana_port may be required to bypass validator constraints. Both syntaxes resolve to adapter configuration values.
Fallback syntax ${varName:-default} is supported; the default is used if the referenced config value is empty or undefined.
- Container names: If you omit
container_name, the plugin generatesiob_<adapterName>_<instance>_<service>(service part may be implicit). - Network: Using
trueinside thenetworks:list signals: attach to the shared networkiob_<adapterName>_<instance>. - Custom network names that are not standard receive the
iob_<adapterName>_<instance>_prefix for isolation.
- Container creation is paused if
iobWaitForReady=true. - The adapter prepares provisioning files locally.
- The plugin copies the specified directories into the declared named volumes.
- Container starts once the adapter signals readiness.
If iobAutoImageUpdate=true, the plugin periodically (or on trigger) checks the registry for a newer tag (same literal tag as declared). On change: pulls, stops container (respecting stop policy), recreates with existing settings, and restarts.
Volumes listed in iobBackup are tagged for inclusion in ioBroker backup routines. Ensure they are named volumes (not anonymous or host bind mounts) for reliable restore.
Containers labeled with iobStopOnUnload=true are stopped when the instance is stopped or disabled — and only those. The plugin does this in its destroy() hook, which the js-controller awaits before the adapter process terminates.
That wait is not unlimited. Once the host has requested the stop, it kills the adapter process after common.stopTimeout milliseconds — 1000 by default — and the adapter itself spends 500 ms of that on its own shutdown. The plugin therefore issues all stops in parallel and skips every avoidable Docker round trip, but it does not shorten the grace period of the containers: that is what stop_grace_period is for, and cutting it short risks data loss for databases.
If your containers need longer than that to shut down cleanly, raise the timeout in the common section of your adapter's io-package.json:
{
"common": {
"stopTimeout": 15000
}
}Pick a value that covers the stop_grace_period of your slowest container plus a little headroom. Without it the container may still be running when the adapter process is killed.
DockerManager can also be used on its own, without the plugin mechanism, to find out whether Docker
is usable on this host. The checkDocker control of a jsonConfig dialog does exactly that, through
the admin instance that serves the dialog:
const dockerManager = new DockerManager({ logger, namespace: 'admin.0', quiet: true });
const info = await dockerManager.getDockerDaemonInfo();
await dockerManager.destroy();The constructor starts the detection right away, so on a host without Docker every created manager
writes Docker is not installed. Please install Docker. into the log of the asking adapter - and a
config dialog with two such checkboxes creates two of them, although that adapter was never asked to
run a container. With quiet: true these messages go to the debug log instead; the result is
returned by getDockerDaemonInfo() anyway, and the caller reports it in its own UI.
Leave quiet unset for a manager that runs containers: there a missing Docker means the containers
will not start, and the user has to see it.
- Keep Compose files minimal—only declare what you manage via the adapter.
- Raise
common.stopTimeoutwhen containers need more than a second to stop—see Stopping containers on unload. - Use explicit named volumes for persistent data you want backed up.
- Avoid hard-coding secrets; prefer environment variables injected via adapter config.
- Test changes on a staging instance before rolling into production.
- (@GermanBluefox) An empty default no longer becomes the number zero:
${config.x:-}handed a literal0to the container when the value was missing, becauseNumber('')is 0. A server URL, a plugin list or a password that asked for "nothing" arrived as"0" - (@GermanBluefox)
devicesis now really passed to the container. It was parsed from the compose file and then dropped by both drivers, and it was excluded from the comparison, which hid the gap. A service that needs a device - a Coral TPU, a video device, a serial adapter - only got it when it also ranprivileged, where docker hands over the whole/devof the host anyway. Container path and cgroup permissions are filled in the way docker does (rwm), on both sides of the comparison, so a device no longer forces a recreate on every check but a changed device does
- (@GermanBluefox) Fixed the endless recreation of containers that use
expose,env_file, a tmpfs volume ornetwork_mode: service:<name>: those settings cannot be read back frominspect, so every check reported them as changed and recreated the container - on every adapter start, and withiobWaitForReadyon every readiness signal - (@GermanBluefox) Fixed the check aborting for a container whose configuration holds a setting the running container does not have at all (a
healthcheckordnsentry, a later addedstop_grace_period): reading it off the missing object threw, so the container was neither recreated nor started nor monitored. A missing setting is now a difference like any other - (@GermanBluefox)
healthcheckis now really applied - it was parsed from the compose file and then dropped, on both the API and the CLI driver - (@GermanBluefox)
dns,dns_searchanddns_optare now really applied - the API driver had them commented out and the CLI driver never rendered them - (@GermanBluefox)
env_filenow works on the API driver as well: the files are read and merged into the environment, as compose does it, withenvironment:winning over the file. Before, only the CLI driver honoured them, so the same compose file behaved differently depending on whether the host offers a docker socket - (@GermanBluefox)
exposeand tmpfs volumes are now passed to docker on both drivers - (@GermanBluefox) Resource limits are read from the compose file:
mem_limit,mem_reservation,memswap_limit,cpus,cpu_shares,cpu_quota,cpu_period,cpusetandpids_limit, withdeploy.resources.limitsas a fallback. Onlyshm_sizewas evaluated before - (@GermanBluefox) Fixed
cpus: the fraction of a CPU was sent as a cpu set, which docker rejects. It is now sent asNanoCpus, andcpusetasCpusetCpus - (@GermanBluefox) The CLI driver no longer builds its command line as a string that a shell has to split again: docker is called with an argument list. Values containing a space - an adapter directory below
C:\Program Files, a password with a blank, a bind mount path, a healthcheck command - arrive as one argument now, and a value can no longer be interpreted as a shell command. This affectsdocker cpof theiobCopyVolumesprovisioning, all container, volume and network commands, anddocker runitself - (@GermanBluefox) Fixed a multi-element
entrypointon the CLI driver:--entrypointtakes a single value, the remaining elements belong behind the image. They used to be joined into one value, which docker looked for as a single executable - (@GermanBluefox) A configuration value that contains a template pattern itself no longer loops forever and blocks the adapter start - it is reported as an error
- (@GermanBluefox) Documented
iobStopOnUnloadcorrectly: likeiobEnabledit is an opt-in and defaults to not stopping the container. The README claimed the opposite - (@GermanBluefox) Removed a leftover
console.logthat wrote past the ioBroker logger on every check of every container
- (@GermanBluefox) Added the
quietoption toDockerManager: a manager created only to probe whether Docker is available now reports its absence on debug level instead of warn/error, so a pure availability check does not fill the log of an adapter that runs no container - (@GermanBluefox) The failed probe of
/var/run/docker.sockno longer goes to the console, but into the debug log of the adapter - it is a normal outcome on a host that uses the CLI, TCP, or has no Docker
- (@GermanBluefox) Fixed the adapter hanging on the first start: creating a container went through
dockerode.run(), which waits for the container to exit and therefore never returned for a long-running container - (@GermanBluefox) Split
containerRun()(starts detached) from the newcontainerRunAndWait()(runs a short-lived container and returns its output). Reading volume directories and files uses the latter and no longer detaches, which also fixes it on the CLI driver - (@GermanBluefox) Documented
iobEnabledcorrectly: it is a required opt-in, a service is only managed if the label is set totrue. Services without it are now logged on debug level instead of being skipped silently - (@GermanBluefox) Fixed
iobStopOnUnload: the plugin did not implementdestroy(), so containers were never stopped when the instance unloaded - (@GermanBluefox) Containers are now stopped in parallel and without the surrounding container listings, to fit into the shutdown timeout of the host
- (@GermanBluefox) Applied
network_modefrom the compose file, includingcontainer:<name>andservice:<name> - (@GermanBluefox) Fixed resolution of
networks: entries declared in the top-levelnetworksblock, thetrueshorthand and option-less entries of the mapping form were dropped silently - (@GermanBluefox) Networks beyond the first one are now connected to the container, external networks are used verbatim
- (@GermanBluefox) Better interpretation of docker-compose files
- (@GermanBluefox) Added support for shm_size
- (@GermanBluefox) Code refactoring.
- (@GermanBluefox) Do not start a manager if no one container is enabled
- (@GermanBluefox) Remove one log line to avoid confusion
- (@GermanBluefox) Added the restarting option
- (@GermanBluefox) Correcting compare of existing and desired container configuration to avoid unnecessary restarts
- (@GermanBluefox) Allowed using entrypoints without a command
- (@GermanBluefox) Packages updated
- (@GermanBluefox) Added support of old volumes with
dockerodelibrary
- (@GermanBluefox) Added
${instance}variable to be used in docker-compose files
- (@GermanBluefox) Added a text file read from volume
- (@GermanBluefox) Split the docker manager into pure docker commands and monitoring of own containers
- (@GermanBluefox) initial release