Docker Engine 29.7 previews containerd inside dockerd
The embedded-containerd experiment changes process placement while retaining runtime endpoints. Stable image mounts and a configurable stop timeout expose different parts of the deployment contract.
Docker Engine 29.7.0 introduces an experimental option to run containerd inside dockerd. The July 30 release notes also graduate image mounts from experimental status and add a daemon-wide default stop timeout. The containerd change interests me because process placement determines how an operator observes and manages a runtime, even when the application image stays unchanged.
I have a strong preference for small service artifacts. A multi-stage build ending with one static Go binary in a scratch image remains my preferred arrangement. I apply a similar interest in explicit dependencies to the host, but a smaller process list is not enough to establish a simpler operational system. The important questions concern which interfaces remain available and which failure modes share a process.
Embedded containerd retains runtime interfaces
The implementation patch describes the experimental embedded-containerd feature. Docker starts the containerd server inside its own process when the feature is enabled through daemon configuration or the feature flag. With the feature disabled, it continues using the configured external containerd.
The internal client uses an in-memory listener. The embedded server also exposes containerd's gRPC API over a Unix socket, or a named pipe on Windows, for the plugin executor and external tools. TTRPC remains available on a platform endpoint so task shims can publish events. Embedding therefore changes how the server runs without removing every endpoint associated with it.
That distinction is useful when evaluating the architecture. An operator should not infer that an in-process server means all runtime communication becomes an ordinary function call. The patch retains explicit transport interfaces for consumers outside dockerd. Troubleshooting needs to account for those consumers as well as the internal client.
The implementation registers only the containerd plugins Docker needs. It omits the CRI, sandbox, streaming, and transfer plugins, along with containerd's NRI and restart monitor. It rejects --cri-containerd in embedded mode instead of silently discarding a request for CRI support. This is a selected runtime configuration, with capabilities chosen for Docker's use.
That selection also prevents an assumption about NRI. Leaving containerd's NRI plugin out of the embedded server does not, on its own, describe Docker's separately integrated NRI behavior. I would keep the two integration points distinct in an architecture review. The component name alone is insufficient to explain which process or subsystem owns a plugin.
Configuration changes which server Docker selects
One ordering decision in the patch deserves attention. Docker checks the embedded feature before considering ContainerdAddr. That allows the experiment to override a default containerd socket supplied by packaged service units. An operator enabling the feature should therefore confirm the effective server selection even if the service file still names an external socket. The feature belongs in the daemon's feature configuration. This example enables it explicitly:
{
"features": {
"embedded-containerd": true
}
}
This is an experiment to evaluate on a suitable test host. I would preserve the previous daemon configuration and identify the runtime state that the evaluation uses before enabling it. The presence of an external service on the machine should not be treated as evidence that dockerd still uses that service.
Distributors also receive a no_embedded_containerd build tag that omits the embedded plugin graph and its dependencies. That makes build configuration part of the capability assessment. Two binaries reporting the same release may need additional packaging context before an operator assumes that the experiment is available.
The Windows implementation derives the named-pipe address from the daemon state directory so multiple daemons can run on one host. It restricts the endpoints to Administrators and LocalSystem. The patch explains that relying on a default security descriptor could expose them more broadly than intended. Endpoint access remains an operational concern after the server moves into the daemon process.
Process placement changes what needs validation
Running two components inside one process makes their process lifecycle shared. I would investigate how that affects failure diagnosis and service management before adopting the experiment. I would also check that existing operational tools can still reach the endpoint they expect. An apparently healthy Docker API would not be my only acceptance criterion if other tooling depends on containerd directly.
The patch does not supply a universal performance result for applications. An in-memory client connection changes one mechanism, but that is insufficient evidence for a throughput or latency claim. I would decide whether to pursue the experiment based on a concrete operational problem. A process count alone would not justify accepting an experimental runtime arrangement.
For compatibility checks, I would use the operations the installation actually relies on. Image pulls and container creation would establish basic operation, while restart and shutdown checks would examine the shared lifecycle. Those checks would need a bounded test environment with state that can be discarded. I would not begin by switching an irreplaceable host merely to confirm that the feature flag parses.
I would also distinguish an external management connection from Docker's internal connection when diagnosing an endpoint failure. The in-memory listener means the internal client does not depend on the same socket path that an external tool uses. An external connection failure would therefore need its own endpoint and access checks before being interpreted as proof that the embedded server has stopped.
A packaging review should identify whether the distributor includes the embedded implementation. The build tag makes that a legitimate variation, and a service configuration cannot add code omitted at build time. For an evaluation record, I would include the binary source and enabled feature alongside the observed runtime behavior. That would help another operator reproduce the experiment with the same packaging choices.
Image mounts become a supported composition option
The graduation change explains that image mounts arrived in API 1.48 and have not required a breaking redesign. Engine 29.7 removes their experimental warning. This is a separate decision from embedding containerd, although image mounts require the containerd image store.
Docker's image mount documentation describes a read-only mount of another image's filesystem into the container. The source image must already exist in the daemon's image store. Creating the mount does not automatically pull it. This gives a deployment another artifact to identify and make available before starting the container.
For a minimal service image, the attraction is the ability to distribute optional tools or assets separately. The documentation also identifies an executable compatibility constraint. A dynamically linked program still needs a matching loader and libraries in the container environment. Mounting files does not make their runtime dependencies disappear.
I would use that constraint to choose a troubleshooting image deliberately. A tool that cannot execute in the target environment adds little operational value. Static tools or data-only mounts avoid that particular dependency, though the operator still needs to trust the source artifact.
The mount also obscures existing files at its destination. A deployment should therefore document the path it occupies and avoid accidentally hiding required application files. I would inspect the container's effective mounts during validation and retain the source image identity in the deployment record. The application image alone no longer describes all read-only content the process can see.
Stop defaults remain subordinate to explicit policy
The new stop timeout is another host-level input. The release's daemon manual documents --default-stop-timeout in seconds. Existing defaults remain ten seconds on non-Windows platforms and thirty seconds on Windows. Zero removes the graceful wait, and negative values are invalid.
The implementation discussion makes explicit container and API-request timeouts authoritative. The daemon option supplies the fallback for containers without their own timeout. I would describe a daemon-wide setting as an operational default, while keeping an application's known shutdown requirement in its deployment configuration. The daemon configuration accepts a timeout in seconds. This illustrative configuration sets the fallback to twenty seconds:
{
"default-stop-timeout": 20
}
That value is an example, with no claim that it suits a particular service. I would choose a real value from the application's shutdown behavior and the surrounding service manager's requirements. An acceptable shutdown test should show the work the process finishes during the allowed interval.
These changes make several host decisions more visible. The embedded server changes component placement, image mounts add a separately distributed filesystem input, and the timeout changes a fallback used during stopping. I would review each setting against the actual installation and record its effective value. That would let an operator explain a container's behavior without assuming that its Dockerfile contains every relevant runtime decision.