Home / Alt manpages / docker-plugin-create(1)

  • docker-plugin-create(1)
  • User command
  • linux

Build a Docker plugin from config.json and rootfs

You will turn a Docker plugin data directory into a local, disabled plugin that Docker can inspect and later enable. The directory must contain a plugin configuration file named config.json and a directory named rootfs. Allow about 20 minutes for a small test plugin, plus time to build the plugin program itself.

This guide covers Docker Community Edition CLI 29.8.1, packaged here as docker-ce-cli 5:29.8.1-1~ubuntu.24.04~noble. The command's interface is small, but the files it consumes describe a privileged extension to the Docker daemon. Treat the configuration and root filesystem as release artefacts, not as an ad-hoc directory to copy from an untrusted source.

1. Confirm the installed command

Run this as your ordinary user:

$ docker --version
Docker version 29.8.1, build 4a63305
$ docker plugin create --help
Usage:  docker plugin create [OPTIONS] PLUGIN PLUGIN-DATA-DIR

Create a plugin from a rootfs and configuration. Plugin data directory must contain config.json and rootfs directory.

The only option is --compress, which compresses the context sent while creating the plugin. It does not compress or rewrite your source directory, and it is not a switch for enabling the resulting plugin.

Checkpoint: the help output must show the two positional arguments in this order: the plugin name, then the data directory. A common distraction is to read PLUGIN-DATA-DIR as the name of the plugin. It is the directory containing the input files.

2. Prepare a separate plugin data directory

Choose a new working directory. The following example creates only a directory layout and a harmless executable placeholder. Replace the placeholder with your actual plugin binary before creating a usable plugin.

$ mkdir -p "$HOME/plugin-build/example/rootfs/usr/local/bin"
$ printf '%s\n' '#!/bin/sh' 'exec /usr/local/bin/example-plugin' > \
    "$HOME/plugin-build/example/rootfs/usr/local/bin/example-plugin"
$ chmod 0755 "$HOME/plugin-build/example/rootfs/usr/local/bin/example-plugin"
$ find "$HOME/plugin-build/example" -maxdepth 5 -print
/home/your-user/plugin-build/example
/home/your-user/plugin-build/example/rootfs
/home/your-user/plugin-build/example/rootfs/usr
/home/your-user/plugin-build/example/rootfs/usr/local
/home/your-user/plugin-build/example/rootfs/usr/local/bin
/home/your-user/plugin-build/example/rootfs/usr/local/bin/example-plugin

The placeholder is only there to demonstrate the path and permission checks. Replace it with a real plugin executable that implements the declared Docker plugin interface before you rely on the result. Do not put secrets, SSH keys, host configuration or an unnecessary device node in this tree. Everything under rootfs becomes part of the plugin context. Keep the source outside a live service directory so that fixing the files cannot disrupt a running service.

3. Write config.json to match the program

Use the configuration format expected by the plugin interface. This small volume-plugin-shaped example declares an entrypoint, a socket and a description. The executable named in Entrypoint must exist inside rootfs; a configuration file does not create it.

{
  "Description": "Example volume plugin",
  "Documentation": "https://example.invalid/example-plugin",
  "Entrypoint": [
    "/usr/local/bin/example-plugin"
  ],
  "Interface": {
    "Socket": "plugin.sock",
    "Types": [
      "docker.volumedriver/1.0"
    ]
  },
  "Linux": {
    "Capabilities": null,
    "AllowAllDevices": false,
    "Devices": null
  },
  "Mounts": null,
  "Network": {
    "Type": "none"
  },
  "Workdir": ""
}

Change the interface type, entrypoint and permissions to match your plugin. Docker's configuration reference also defines fields for mounts, environment variables, arguments, capabilities, devices, host namespaces and propagated mounts. These are requests for access, not harmless metadata. Add only what the plugin genuinely needs, and have the plugin author review every capability, device and host-namespace setting.

Check the JSON before sending it to Docker:

$ python3 -m json.tool "$HOME/plugin-build/example/config.json" > /dev/null
$ test -d "$HOME/plugin-build/example/rootfs"
$ test -x "$HOME/plugin-build/example/rootfs/usr/local/bin/example-plugin"
true

That proves only that the configured path exists and is executable. Do not treat a syntactically valid JSON file as a working plugin. The entrypoint program must also implement the declared interface, and the entrypoint, interface and rootfs contents must agree.

4. Inspect the complete input before creation

Review names, permissions and file types without changing anything:

$ find "$HOME/plugin-build/example" -xdev -printf '%M %u:%g %p\n' | sort
$ stat "$HOME/plugin-build/example/config.json" \
    "$HOME/plugin-build/example/rootfs"
$ sed -n '1,220p' "$HOME/plugin-build/example/config.json"

Check that the data directory contains exactly the intended config.json and rootfs at its top level. Do not use a broad directory such as your home directory as PLUGIN-DATA-DIR. That makes accidental inclusion of unrelated files much easier to miss.

5. Create the disabled local plugin

Creating a plugin changes Docker daemon state. It does not start the plugin, but it registers a new local plugin name. Make sure the daemon is the intended Docker host and that the name is not already in use. This command normally needs access to the Docker socket; use sudo only when your Docker installation requires it.

$ docker plugin create example/plugin:trial \
    "$HOME/plugin-build/example"
example/plugin:trial

With --compress, the same operation is:

$ docker plugin create --compress example/plugin:trial \
    "$HOME/plugin-build/example"
example/plugin:trial

Do not run both commands for the same name. If creation succeeds, Docker prints the plugin name. The plugin remains disabled until you explicitly enable it.

Checkpoint: if Docker reports lstat ...: no such file or directory, the data-directory path is wrong or has disappeared. If it reports a missing config.json or rootfs, fix the source directory and retry. A failed create does not justify running the command with a wider path.

6. Verify the result before enabling it

List the plugin and inspect its configuration:

$ docker plugin ls
ID             NAME                   DESCRIPTION              ENABLED
...            example/plugin:trial  Example volume plugin    false
$ docker plugin inspect example/plugin:trial

The exact ID and table spacing vary. The useful state is ENABLED false. Inspect the capabilities, devices, mounts, network type, entrypoint and environment rather than relying on the description alone. If a field is broader than the plugin requires, remove it from config.json and create a new test name after cleaning up the incorrect local plugin.

Enabling is a separate, security-sensitive action and is outside this creation step. A plugin may request network access, host namespaces, Linux capabilities, devices or a writable mount. Review those requests with the same care as a service running with elevated host access.

7. Recover from a bad local creation

Do not delete a plugin blindly if another workload might use it. First check its state and references. When you have confirmed that the test plugin is unused and disabled, remove the exact name you created:

$ docker plugin disable example/plugin:trial
$ docker plugin rm example/plugin:trial

If it was already disabled, the first command may report that no action is needed. If the plugin is enabled or in use, stop the dependent workload through its normal change process instead of forcing removal. The source directory remains on disk, so you can correct config.json or rootfs and create a new local tag without rebuilding the original inputs.

Done means

  • The installed Docker CLI version and command syntax were confirmed.
  • The data directory contains the intended config.json and rootfs, with no accidental secrets or host files.
  • The entrypoint in the configuration exists in the root filesystem and has the required executable permission.
  • JSON syntax and the complete input tree were checked before creation.
  • docker plugin ls shows the new plugin as disabled.
  • Capabilities, devices, mounts and namespace access were reviewed before any enable operation.