February 19, 2025·6 min read·engineeringdockerimagesnetworking

Docker Engine 28 makes image selection and network intent explicit

Engine 28 adds image mounts and platform selection while repairing packet filtering around published ports.

A host port mapping changes a packet's destination before the container receives it. That creates a security problem when filtering relies on an address that translation has already replaced. Docker Engine 28 repairs two cases where neighboring hosts could exploit that ordering, including access to ports published on loopback addresses. The mechanism is a useful starting point for a release whose broader theme is making image and network choices explicit.

Docker published Engine 28.0.0 on February 19. Its visible additions include image mounts and platform selection for image archives and history. Those changes concern which artifact a container or operator means to use. The networking changes address the equally concrete question of which path should allow a connection to reach it.

I consider Docker's repeatable image description its most useful contribution. Explicit selection improves that model when an image name refers to several platform variants or when a container needs data distributed in another image. The packet filtering work matters for a different reason. The runtime must preserve the access restriction expressed by a host address.

Destination translation can defeat an address restriction

The packet filtering patch explains two failures with a common cause. Docker created destination NAT rules for published ports without sufficiently filtering packets before translation. A neighboring machine on the same layer-two segment could reach a container in ways the port mapping did not intend to permit.

The first case concerns direct access to the container address. The pull request uses a container listening on port 5000, published through host port 6000. A neighbor could address the container's port 5000 directly. Publishing through 6000 did not, by itself, exclude that alternate route.

The repair adds filtering in the raw table's PREROUTING chain. That position matters because the packet still has its original destination. Once the NAT rule has rewritten the destination to the container, later processing cannot treat the translated destination as evidence that the sender used the intended host endpoint.

This filtering applies to the NAT gateway mode. The patch explicitly leaves the unprotected variant without that filtering. The distinction is an intentional network configuration choice, so an explanation that promises universal blocking would overstate the fix.

The second case concerns host ports bound to loopback. Linux ordinarily rejects remotely received packets whose destination is a loopback address. The patch explains that applying the destination NAT rule could avoid that ordinary rejection. A neighboring host could therefore send traffic to a loopback mapping that was intended to be inaccessible remotely.

Docker now drops packets with loopback destinations when they arrive through an interface other than loopback. The implementation includes an exception for the loopback0 interface used by WSL2 mirrored networking. That detail shows why a correct fix needs to account for the host networking arrangement as well as the address written in a command.

A loopback binding is a statement about who may connect. Its usefulness depends on filtering preserving that statement before translation changes the packet. I regard this as a more consequential change than a new command flag, because an apparently restrictive existing configuration needs the runtime to enforce its meaning.

A multi-platform image needs a selected manifest

The CLI platform-selection change adds --platform to docker load, docker save, and docker history for the containerd image store. An image that contains several platform variants needs a way to identify the one an operation concerns. History, in particular, needs a specific image whose build history it can display.

The corresponding Engine API patch makes the choice available to callers beyond the Docker CLI. History accepts a platform parameter encoded as an OCI platform object. The load and save operations accept the same kind of parameter. Omitting it from load or save retains the full multi-platform image.

For example, an operator exporting an available Linux AMD64 variant can select it explicitly. The image name below is illustrative, and the selected variant must already be present in the image store.

docker image save --platform=linux/amd64 -o service-amd64.tar example/service:release
docker image load --platform=linux/amd64 -i service-amd64.tar
docker image history --platform=linux/amd64 example/service:release

The useful distinction is between the image reference and a platform-specific member. Keeping a multi-platform image under one name is convenient for distribution. An offline archive or a history inspection can have a narrower purpose. The flag lets the caller describe that purpose without first arranging a different reference.

It also creates a meaningful error case. The release notes state that loading a requested platform which is not loaded produces an error. Selecting a platform is not a request to manufacture a missing variant. It limits the operation to a variant the supplied artifact can actually provide.

This is relevant to archive-based deployment workflows because the archive becomes a deliberate subset of the available image. The receiving operator can distinguish an archive made for one architecture from an unfiltered export. That distinction follows from the selection operation itself and does not require assuming anything about a particular registry or deployment system.

Images can supply mounted data

The image-mount implementation adds an image mount type to container creation. Docker exposes it through --mount type=image. The API accepts a mount of type image, and the release also permits an image-subpath option to select a particular path within the source image.

The original proposal explains the motivation through immutable data. Configuration files, scripts, and package repositories can be useful inside a container without being part of its executable image. An image provides an existing distribution format for that content.

That proposal is historical context, not a substitute for the shipped implementation. Its examples discuss possible service behavior and fetching rules. The Engine 28 implementation confirms the new mount type and describes reusing filesystem construction logic to create a filesystem from an image. Claims about every proposed integration would need separate evidence.

The design separates the image used to create the container from an image supplying mounted files. That permits different version choices for executable content and supplementary data. It also means a container's effective file inputs include the mount definition. Recording only its main image reference would leave part of that definition unspecified.

My preference for small service images makes this separation interesting. A final runtime image can remain focused on its executable while another image packages files with a different update schedule. Whether that arrangement is worthwhile depends on whether the files actually need an independent release process.

Default gateways become a declared choice

Multi-network containers receive a more direct way to choose their default gateway. The gateway-priority API change adds GwPriority to endpoint settings. Callers can supply it when creating the container or when connecting another network.

The endpoint with the highest priority supplies the default gateway. Equal priorities fall back to sorting network names lexicographically and choosing the first. An explicit priority therefore removes a dependency on names when one network should carry default traffic.

The CLI exposes this as gw-priority, with the extended network syntax required for docker run. This is separate from choosing an interface name. A name identifies an interface, while gateway priority tells Docker which connected network should supply the default route.

Network names still matter when priorities tie. Two endpoints with equal values do not represent an unspecified random choice under the documented rule. Renaming one network can change which endpoint sorts first. Assigning distinct priorities records the desired relationship in the container definition, where a reader can inspect it without inferring routing intent from naming conventions.

Endpoint-specific sysctls also become more explicit. The API 1.48 change stops automatically migrating settings for eth0 from container-wide sysctl options to a network endpoint. The patch gives a rejected form and its replacement.

docker run --network name=mynet,driver-opt=com.docker.network.endpoint.sysctls=net.ipv4.conf.IFNAME.log_martians=1 example/service:release

Here mynet and the image reference are illustrative. The important syntax is the endpoint driver option and the IFNAME placeholder supplied in the documented replacement. It attaches the setting to the network endpoint without assuming which automatic interface name Docker will choose.

Engine 28 thus asks callers to state more of the identity and routing information that was previously implicit. The platform parameter selects a member of a multi-platform artifact. Image mounts declare additional filesystem inputs, and gateway priorities identify the intended default route. For published ports, the implementation now filters the original destination before NAT can obscure the restriction expressed by the mapping.