Load containerd.d/*.toml next to the daemon configuration file when
using embedded containerd.
Only the plugins, disabled_plugins, and required_plugins settings
apply.
Require version 4 or later and reject nested imports, so the merge
order follows the lexical order of the files.
Warn when drop-ins are present but dockerd uses a system or managed
containerd, which ignore them.
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Pass the daemon lifecycle context to the config reload trap and stop the
Unix signal handler when the context is canceled. This also unregisters
the SIGHUP notification when the goroutine exits.
On Windows, signal the Win32 event when the context is canceled to wake
the blocking wait and allow the reload goroutine to exit.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Fall back to the embedded containerd implementation when no system containerd
is configured or running and the managed containerd binary cannot be found.
Keep other managed-containerd startup failures fatal so configuration,
permission, and runtime errors are not silently masked by the fallback.
Apply the same behavior on Unix and Windows.
This moves the default fallback toward the embedded implementation, allowing
the managed-containerd path to be deprecated or removed in the future.
With this patch, the daemon can be started without the containerd binary present;
command -v containerd || echo 'containerd binary not found'
containerd binary not found
dockerd
INFO[2026-08-16T15:05:35.341440421Z] Starting up
INFO[2026-08-16T15:05:35.344028963Z] containerd not running, starting managed containerd
INFO[2026-08-16T15:05:35.344604171Z] containerd binary not found, starting embedded containerd error="failed to find containerd binary: containerd: exec: \"containerd\": executable file not found in $PATH"
WARN[2026-08-16T15:05:35.344616963Z] Running with experimental embedded-containerd mode
INFO[2026-08-16T15:05:35.344720338Z] starting embedded containerd server address=/var/run/docker/containerd/containerd.sock
...
INFO[2026-08-16T15:05:36.191205463Z] Daemon has completed initialization
INFO[2026-08-16T15:05:36.191244088Z] API listen on /var/run/docker.sock
One thing worth noting is that it currently does not check for the shim to
be present; the daemon starts successfully;
command -v containerd-shim-runc-v2 || echo 'containerd shim not found'
containerd shim not found
dockerd
...
INFO[2026-08-16T15:05:36.191205463Z] Daemon has completed initialization
INFO[2026-08-16T15:05:36.191244088Z] API listen on /var/run/docker.sock
Pulling images also works, but the container fails to start;
docker run --rm --quiet alpine
docker: Error response from daemon: failed to create task for container: failed to start shim: failed to resolve runtime path: runtime "io.containerd.runc.v2" binary not installed "containerd-shim-runc-v2": file does not exist
Daemon logs;
INFO[2026-08-16T15:08:56.091416375Z] image pulled digest="sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b" remote="docker.io/library/alpine:latest"
ERRO[2026-08-16T15:08:56.134184333Z] copy stream failed error="reading from a closed fifo" stream=stdout
ERRO[2026-08-16T15:08:56.134201042Z] copy stream failed error="reading from a closed fifo" stream=stderr
ERRO[2026-08-16T15:08:56.139684375Z] Handler for POST /containers/{name:.*}/start returned error error-response="failed to create task for container: failed to start shim: failed to resolve runtime path: runtime \"io.containerd.runc.v2\" binary not installed \"containerd-shim-runc-v2\": file does not exist" method=POST module=api request-url=/v1.55/containers/5cdb5c75a4c5e2b6f8a46c14258d9aded81d7e8fd824a1158d613f8704356ecd/start status=500 vars="map[name:5cdb5c75a4c5e2b6f8a46c14258d9aded81d7e8fd824a1158d613f8704356ecd version:1.55]"
INFO[2026-08-16T15:09:29.483493418Z] image pulled digest="sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b" remote="docker.io/library/alpine:latest"
ERRO[2026-08-16T15:09:29.518634168Z] copy stream failed error="reading from a closed fifo" stream=stdout
ERRO[2026-08-16T15:09:29.518660043Z] copy stream failed error="reading from a closed fifo" stream=stderr
ERRO[2026-08-16T15:09:29.524466210Z] Handler for POST /containers/{name:.*}/start returned error error-response="failed to create task for container: failed to start shim: failed to resolve runtime path: runtime \"io.containerd.runc.v2\" binary not installed \"containerd-shim-runc-v2\": file does not exist" method=POST module=api request-url=/v1.55/containers/6ca6224313a2bdc595d108702f89fe88abd97b6ebe021ddcc8b0dce402057cfa/start status=500 vars="map[name:6ca6224313a2bdc595d108702f89fe88abd97b6ebe021ddcc8b0dce402057cfa version:1.55]"
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Always return a containerd shutdown wait function after successful
initialization, including when using an externally managed system containerd,
where the function is a no-op.
Use the returned function during the normal shutdown sequence, after the API
server has stopped, so managed and embedded containerd instances have finished
shutting down before tracing is torn down. Keep a deferred context cancellation
as a safeguard for early returns.
Bound OpenTelemetry shutdown to avoid blocking daemon shutdown indefinitely,
and defer returning ServeAPI errors until containerd and tracing shutdown have
completed.
Also report failures while waiting for containerd to stop instead of silently
discarding them.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Run containerd inside dockerd when the experimental
`embedded-containerd` feature is enabled through `--feature` or the
daemon configuration.
Serve containerd's gRPC API on a Unix socket, or a named pipe on
Windows, for the plugin executor and external tools.
Use an in-memory listener for dockerd's own containerd client. Serve
TTRPC on a platform endpoint so task shims can publish events.
Register only the containerd plugins dockerd needs. Leave CRI, sandbox,
streaming, transfer, NRI, and the restart monitor out of the embedded
server.
Reject `--cri-containerd` when embedded mode is enabled instead of
silently ignoring the requested CRI support.
Check the feature before `ContainerdAddr` so it can override the default
containerd socket supplied by packaged service units. Continue to use
the configured external containerd when the feature is disabled.
Derive the Windows named-pipe address from the daemon state directory so
multiple daemons can run on the same host. Restrict the pipes to the
built-in Administrators group and LocalSystem with the same protected
DACL as dockerd's API pipe, as the default security descriptor depends
on the process token and can expose the containerd endpoints more
broadly than intended. Treat embedded mode as a containerd runtime in
the Windows test environment.
Add the `no_embedded_containerd` build tag so distributors can omit the
embedded plugin graph and its dependencies.
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Previously the daemon forced its umask to 0022, which silently stripped
the group/other-write bits from any explicitly requested file mode.
This caused e.g. `COPY --chmod=777` to result in 0755 rather than 0777.
Clear the umask so explicit modes are honored.
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>