August 21, 2018·4 min read·engineeringdockercontainerdruntime

Docker disables the embedded containerd CRI plugin

Docker 18.06.1 stops its managed containerd from starting the CRI plugin by default. The change addresses an unexpected listener on port 10010 and clarifies the difference between embedded and standalone runtime configurations.

A host port can be occupied even when no container publishes it. After upgrading to Docker 18.06.0-ce, one operator found that an application could no longer bind port 10010 because docker-containerd already used it. Docker 18.06.1 disables the embedded runtime's CRI plugin by default. The August 21 release notes describe the change in terms of its listener, but the implementation disables the plugin in the containerd process managed by Docker.

The distinction matters because containerd can run in more than one arrangement. A runtime started as a child of dockerd has a different role from a separately configured standalone service. The patch in Docker Engine pull request 29 explicitly applies the new default to the child-process arrangement. It does not announce the removal of CRI support from containerd as a project.

I value Docker's ability to make an application's environment explicit. An unexpected host listener is difficult to reconcile with that goal when the application's own configuration contains no use of the port. This correction makes the supporting runtime's default behavior better match the role it has inside Docker.

The port conflict identified the unexpected component

The original report in Moby issue 37507, opened July 20, describes several services behind an Nginx reverse proxy. Their host ports followed an existing allocation pattern. After a reboot, a service on 10010 failed with an address-in-use error, and investigation identified docker-containerd as the process occupying the port.

Moving the application to another port allowed it to start, but that did not answer why the runtime had taken the original allocation. The report asks whether the listener is configurable. It includes engine version information and states that the service port was occupied without a corresponding container using it. Those facts distinguish the failure from an ordinary collision between two published application ports.

This is also why container listings alone can be incomplete evidence when investigating a host bind failure. A host process can own the socket outside the application containers. In the reported case, the error came from attempting to start Docker's userland proxy for the application, while the conflicting process belonged to the runtime supporting Docker itself.

The issue is evidence of a port collision in one deployment, not a measurement of all affected hosts. It establishes that the default listener can interfere with an existing application allocation. The patch then identifies the general configuration boundary that Docker changes to prevent that unnecessary listener in its managed arrangement.

The patch changes plugin configuration

The patch is small. In the Linux remote runtime defaults, it appends cri to the disabled plugin list and removes the corresponding plugin configuration entry. Its comment says that the plugin should be disabled by default when containerd is managed as a child of dockerd. That is more specific than merely hiding a network socket.

The commit message explains that Docker 18.06 did not offer a configuration option to disable this plugin, and that the plugin was not particularly useful in this embedded arrangement. Docker can therefore choose the appropriate default at the point where it constructs the runtime configuration. Users do not need to invent a separate container-level port workaround for the embedded process.

Disabling a plugin and changing the application's port have different effects. A port change leaves the runtime's extra service in place and changes an application deployment to accommodate it. Disabling the unnecessary plugin addresses why the port was consumed. For hosts already relying on a planned port allocation, that is a more direct correction to the reported cause.

The scope remains important when interpreting the fix. A standalone containerd installation may intentionally provide CRI services. The patch concerns containerd configured and started by Docker, where Docker owns the child process's defaults. It should not be read as advice to remove the plugin from every environment in which containerd appears.

A runtime update is a separate change

The release also updates containerd to 1.1.2. The Engine pull request 33 patch changes the pinned daemon revision used by the installer. Its commit message notes the associated CRI version update and a maximum container log line size setting. That dependency change is distinct from the patch disabling the embedded plugin.

Keeping the two changes separate avoids an incorrect explanation of the fix. Updating the runtime version does not, by itself, express Docker's intended plugin policy. The disabling code is visible in a different patch, at the point where Docker assigns defaults. The maintenance release carries both the dependency update and the policy change.

Another builder correction in Engine pull request 25 addresses an empty expanded base image name. The patch explains that an empty string could reach code interpreting it as FROM scratch. It now rejects the empty expansion with an error. That is a separate example of a default acquiring a meaning that the user did not explicitly request.

For a Dockerfile using a variable as its base image, an absent value should not silently select an empty filesystem. The new check stops stage initialization when the expanded base name is blank. Its regression test supplies a variable reference without a value and expects an error, so the intended failure is recorded directly.

The diagnostic includes the original base-name expression. An operator can therefore connect the blank result to the variable reference in the recipe, instead of receiving an error that has already discarded the expression. This is especially useful when the build command and Dockerfile are maintained separately.

I find these maintenance changes useful because their effects can be traced to exact configuration decisions. The runtime patch prevents Docker's child containerd process from starting an unnecessary plugin. The builder patch prevents an empty substitution from selecting an unintended base. In the port-conflict case, the operational result is that the managed runtime no longer consumes 10010 through that default CRI listener.