Container engines in 2026: revisit Docker Engine, Podman and containerd before you install another container daemon

On most teams, installing a container engine is the first thing done on a new machine and the last thing anybody thinks about. A new VM comes up, someone runs the install script they used three years ago, adds a few users to the docker group, and moves on. A new CI runner gets the same treatment, plus one line that mounts the Docker socket into every job. A new laptop gets whatever the developer already knows. It works, so nobody revisits it.

The software under that habit has changed a lot. Docker Engine is now on the 29 line, with the containerd image store as the default for fresh installs and cgroup v1 on its way out. Podman 6.0 removed cgroup v1, iptables, CNI and slirp4netns support in one release, and the project now lives in a CNCF-owned GitHub organisation. containerd has a yearly long-term release, a regular release on top, and a long list of 2026 advisories in its Kubernetes (CRI) code. And every one of these projects, plus the low-level runtimes runc and crun, shipped security fixes this year.

This post compares the three engines most teams choose between in 2026: Docker Engine (built from the Moby project), Podman, and containerd used directly with nerdctl. We look at them on servers, CI runners and laptops: rootful and rootless, daemon and daemonless, Quadlet, Compose, socket security, licences (Docker Desktop’s terms are not Docker Engine’s), 2026 versions and security releases, runc and crun underneath, and Kubernetes. Then we work through a plan for one invented team.

Neighbouring topics already have their own posts, so we only touch them here. Building images is covered in image builds with BuildKit, Buildah and Kaniko. Running untrusted code in stronger isolation is covered in container sandboxing with gVisor, Kata Containers and Firecracker. Small clusters are covered in lightweight Kubernetes with k3s, k0s and Talos Linux. And CI agents, which is where many container engine mistakes live, are covered in self-hosted CI with Jenkins, Woodpecker CI and Tekton.

A scenario to keep us honest

Take an invented Jabalpur company that builds billing, milk collection and payment software for dairy cooperatives and agri-input dealers across Madhya Pradesh and Chhattisgarh. About 30 engineers, with a three-person platform team. The company itself has grown to a little over 260 employees, most of them in field support and sales.

Their containers today:

  • Fourteen Ubuntu VMs in a cloud region, set up in 2023 with the Docker convenience script. They run Docker Engine 24 and 25, never upgraded beyond the package updates that happened by chance. Most applications run with docker compose up -d from someone’s home directory; a few use docker run commands kept in a wiki.
  • Six engineers are in the docker group on every VM, because that was the easy way to stop typing sudo.
  • Two CI runner VMs. Every pipeline job runs in a container that has /var/run/docker.sock mounted, so jobs can build and push images.
  • A small k3s cluster for the newer services, using its bundled containerd.
  • Laptops: about 20 Macs and Windows machines with Docker Desktop, and the rest Linux with Docker Engine.

Two things started the discussion. First, the finance head read the Docker Desktop licence terms during a software audit and noticed that the company had crossed 250 employees. Second, an outside security review flagged the CI runners: “Any pipeline, including one from a branch with a malicious dependency, can control the Docker daemon on the runner, and that daemon runs as root.” The reviewer also pointed out that the Engine version on the VMs was older than the fixes for several 2026 Docker advisories, including one where copying an archive into a container could run a binary from the container as root on the host.

The proposals came quickly. “Move everything to Podman, no daemon, no root, no licence questions.” “Kubernetes already uses containerd, just use nerdctl everywhere.” “Buy Docker Business and stop worrying.” The CTO asked a fair question: which of these actually fixes the problems the reviewer found?

The question: what should this team run on servers, CI runners, laptops and Kubernetes nodes, and what must change apart from the software?

The short version

  • Docker Engine (Moby, Apache 2.0) is a daemon, dockerd, that drives containerd and runc underneath. The current line is 29, with 29.9.2 released on 20 October 2026. Docker Engine 29 made the containerd image store the default for fresh installs, added nftables support as an experimental option, and deprecated cgroup v1 (support continues until at least May 2029). Docker Engine is free open source software; Docker Desktop is a separate product under a commercial subscription agreement.
  • Podman (Apache 2.0) is daemonless and was designed rootless-first. The current line is 6.1, with 6.1.4 released on 12 October 2026, and 5.8 still getting patch releases (5.8.9 on the same day). Podman 6.0 (24 June 2026) dropped cgroup v1, iptables, CNI, slirp4netns and BoltDB. Podman runs services under systemd through Quadlet files, and offers a Docker-compatible API socket when you want one. Podman, Buildah and Skopeo together are a CNCF Sandbox project since January 2025.
  • containerd (Apache 2.0, CNCF graduated) is the runtime under Docker Engine, k3s and many managed Kubernetes services. It is now at 2.4.2 (13 October 2026), with 2.3 as the long-term release supported until 30 April 2028. nerdctl (2.4.1, 2 October 2026; 2.4.0 in September focused on matching Docker 29 behaviour) gives it a Docker-like command line with Compose support.
  • Underneath all three sits an OCI runtime: runc (1.5.2, 25 September 2026) or crun (1.30.1, 25 September 2026).
  • Kubernetes talks to containerd or CRI-O through the CRI. Docker Engine has not been a direct Kubernetes runtime since dockershim was removed in Kubernetes 1.24.

If you remember one thing: the engine name matters less than who can talk to it and with what privileges. Membership of the docker group, a mounted socket or a TCP API is root on that machine. Fix that first, keep whatever engine you choose on a supported, patched version, and only then decide whether switching engines is worth the effort.

Where things stand on 28 October 2026

Docker Engine. The 29 line started with 29.0.0 on 10 November 2025, a release with several breaking changes: the containerd image store became the default for fresh installs (not when userns-remap is set), the daemon requires a minimum API version, Docker Content Trust was removed from the CLI, and cgroup v1 was deprecated. Later 29.x releases added experimental NRI support (January 2026) and an experimental embedded containerd mode (July 2026). 29.9.0 (8 October) moved to BuildKit 0.34.0, 29.9.1 (14 October) to containerd 2.4.2 and RootlessKit 3.3.0, and 29.9.2 (20 October) to Go 1.26.10. Docker Compose is now on its v5 line: 5.0.0 (December 2025) skipped versions 3 and 4 to avoid confusion with the old Compose file versions and handed builds to Bake. Compose 5.6.1 came out on 21 October 2026.

Docker Desktop. Docker Desktop 4.97.0 was released on 26 October 2026. Its licence page says it is free for small businesses (fewer than 250 employees and less than 10 million US dollars in annual revenue), personal use, education and non-commercial open source projects, and needs a paid subscription for professional use in larger organisations and for government entities. The same page says the licensing of Docker Engine and the other Moby open source projects is not changing.

Podman. Podman 6.0.0 (24 June 2026) is the biggest break in years. It needs matching Buildah, Skopeo, Netavark and Aardvark versions, and drops cgroup v1, iptables (use nftables), CNI (use Netavark), slirp4netns (use pasta), BoltDB (automatic migration to SQLite), Intel Macs and Windows 10. Network isolation is now on by default, and podman volume prune removes only anonymous volumes unless you pass --all. The Go import path moved to go.podman.io/podman/v6 as part of the move into a CNCF-owned GitHub organisation. 6.1.4 (12 October) is the latest stable, and 6.2.0-rc1 appeared on 26 October. Podman Desktop is at 1.30.5 (13 October 2026, Apache 2.0).

containerd. containerd publishes one LTS release a year, with regular releases supported for about eight months in between. Its release table shows 2.4 as active (16 September 2026 to 16 May 2027), 2.3 as LTS (30 April 2026 to 30 April 2028), 2.2 as active until 6 November 2026, 2.0 as LTS until March 2027, and 1.7 at end of life since 30 September 2026. 2.4.0, the first release after an LTS, removed deprecated options such as enable_cdi (CDI is always on) and the CNI bin_dir setting. On 13 October the project released 2.4.2, 2.3.7, 2.2.10 and 2.0.14 together with security fixes. For Kubernetes 1.37, the table lists containerd 2.4.0+ and 2.3.0+ as supported.

runc and crun. runc 1.5.0 came out on 19 June 2026. With it, runc 1.2 and older became unsupported, 1.3 moved to high severity CVE fixes only until the end of October 2026, and 1.4 to security and significant bug fixes. runc 1.5.2 (25 September) includes a workaround for a Linux kernel bug, present from kernel 6.17 and fixed in 7.2, that caused random runc crashes when setting device rules on cgroup v2. A 1.6.0 release candidate appeared on 6 October. crun 1.30 (24 September) and 1.30.1 (25 September) are the latest crun releases.

Kubernetes. The supported Kubernetes minor releases are 1.37, 1.36 and 1.35; 1.34 reached end of life on 27 October 2026. CRI-O, the other CNCF graduated CRI runtime, released 1.37.2 and patch releases for 1.34 to 1.36 on 2 October 2026.

Security in 2026: the year the boundaries got tested

The 2026 advisories show where the real risk sits.

  • Docker Engine. In March, CVE-2026-34040 (high) allowed an oversized request body to bypass authorisation plugins; it was fixed in 29.3.1 and only matters if you use AuthZ plugins. In May, CVE-2026-41567 (high) showed that uploading a compressed archive into a container with PUT /containers/{id}/archive could run a decompression binary from the container’s filesystem as root on the host; CVE-2026-42306 (high) and CVE-2026-41568 (medium) were race conditions in docker cp. All three were fixed in 29.5.1. On 1 October, CVE-2026-92543 (high) described how a DNS answer mixing a loopback address with an attacker’s address could make the daemon treat a registry as insecure, fall back to HTTP or skip certificate checks, and leak registry credentials; it was fixed in 29.8.2. 29.9.0 also picked up Go fixes for four HTTP/2 denial of service issues.
  • Podman. CVE-2026-57231 (high, fixed in 6.0.0) let a malformed image leak host environment variables into containers. CVE-2026-94603 (critical, fixed in 6.1.3 and 5.8.8 on 29 September) was serious: podman run on an image carrying a checkpoint annotation ignored the user’s sandboxing options, so --cap-drop=ALL could silently be replaced with whatever the image asked for. Podman 6.1.3 removed support for running checkpoint images with podman run altogether. Smaller 2026 fixes covered a WORKDIR symlink traversal, podman quadlet install --replace leaving old content in files, and a PowerShell injection in the Hyper-V machine provider. 6.1.4 updated gvproxy for CVE-2026-107935.
  • containerd. Most 2026 advisories are in the CRI plugin, which means Kubernetes nodes. CVE-2026-46680 (May) let a crafted numeric User value bypass runAsNonRoot and run as UID 0; the fix did not come to 2.1, which had already reached end of life. CVE-2026-53488 (critical, June) passed image LABEL values to containers without validation, which could lead to command execution on the host through a plugin that acts on labels. A group of critical checkpoint restore issues followed in June and September (CVE-2026-53492, CVE-2026-50195, CVE-2026-53489 and CVE-2026-95837). The October releases fixed image pull crashes and disk exhaustion from malicious registries (CVE-2026-95839 and two advisories without CVE IDs).
  • runc and crun. runc’s CVE-2026-41579 (low, June) let an image with /dev as a symlink cause limited changes on the host; the advisory notes that Docker is not affected because of the way it sets up its init layer, while Podman and containerd users were. crun fixed several symlink issues around /dev/console and /dev/null (CVE-2026-88264, CVE-2026-88265) and a high severity issue in rootful krun with passt (CVE-2026-84042).

Three lessons come out of this list. First, images are untrusted input: labels, annotations, environment entries, /dev symlinks and tar files inside images were all attack paths this year, in every engine. Scanning helps (see vulnerability scanners with Trivy, Grype and OSV-Scanner), but the real control is where images come from. Second, unused features are still attack surface: checkpoint and restore alone produced critical advisories in Podman and containerd. Third, patch speed matters more than engine choice; the teams at risk are the ones still running 2023 versions.

First question: why do you want to change?

Before comparing engines, write down why the discussion started. In the Jabalpur case, look at each trigger:

  • The CI runner finding is about the socket. Any engine with a root-owned API socket mounted into a job has the same problem. Podman can expose a Docker-compatible socket too, and mounting it would recreate the risk. The fix is to stop giving jobs control of a root daemon.
  • The old Engine version is an upgrade problem. Docker Engine 29.9.2 has the fixes. Moving to Podman would also fix it, but so would apt upgrade with the right repository and a test plan.
  • The docker group is a privilege problem. Docker’s own post-install documentation warns that the group grants root-level privileges. Six people with silent root on fourteen VMs is the issue, not the engine.
  • The Docker Desktop question is a licence and budget question about laptops only. It has nothing to do with servers, because Docker Engine on Linux is Apache 2.0.

So the first decision is not Docker versus Podman. It is: who may start containers on each kind of machine, through which interface, with what privileges, and on which supported version.

Rootful, rootless, daemon and daemonless

A daemon is a long-running service that owns containers. Docker Engine’s dockerd is one, and it talks to containerd, which starts each container through a small shim and an OCI runtime. Clients such as the docker CLI and Compose send requests to its API over a Unix socket.

Daemonless means there is no central service. Podman’s CLI forks the OCI runtime directly, with a small monitor process called conmon per container. Containers belong to the user who started them, and systemd can supervise them like any other service. Podman can still run podman system service to offer an API socket, usually from systemd, when tools need one.

Rootful means the engine runs as root on the host. A process inside the container may run as root or not, but the engine itself has full power, and anyone who controls it can mount the host’s / into a container.

Rootless means the engine and containers run as a normal user inside a user namespace. Root inside the container maps to an unprivileged user on the host. Docker supports this as Rootless mode, where dockerd itself runs as your user through RootlessKit; Docker’s documentation is clear that this is different from userns-remap, where the daemon still runs as root. Podman runs rootless by default when a normal user runs it. containerd and nerdctl support rootless through a setup tool as well.

Rootless is not free. You need subordinate user and group ID ranges (/etc/subuid and /etc/subgid), user namespace support in the kernel, cgroup v2 with delegation if you want resource limits, and a user-mode network stack such as pasta, which adds some overhead and has limits around low ports and source IPs. But for CI runners and developer laptops, the trade is usually worth it: a container escape lands as an unprivileged user, not as root.

What each engine is

Docker Engine (Moby)

Docker Engine is the open source engine built from the Moby project: dockerd, the docker CLI, the Compose plugin and the Buildx plugin with BuildKit. It runs on Linux, and on macOS and Windows it runs inside a Linux VM managed by Docker Desktop or by another tool. It is the most widely documented engine, assumed by many tutorials, CI templates and vendor guides, and it includes Swarm mode for simple multi-host scheduling. Its storage now uses containerd’s image store on fresh installs, which brings lazy pulling with snapshotters such as stargz and SOCI.

Podman

Podman is a daemonless, rootless-first engine from the same family as Buildah and Skopeo. Its CLI is deliberately close to Docker’s, so most docker commands work with podman in their place. It adds pods (groups of containers sharing a network namespace, like a Kubernetes pod), podman kube play to run Kubernetes YAML locally, podman generate kube to go the other way, and Quadlet, a systemd generator introduced in Podman 4.4 (February 2023) that turns small .container, .pod, .volume, .network and .kube files into systemd units. On macOS and Windows, podman machine manages the Linux VM, and Podman Desktop adds a graphical interface.

containerd with nerdctl

containerd is a runtime daemon designed to be embedded: Docker Engine, k3s and many managed Kubernetes services use it, and it exposes a gRPC API rather than a user-facing CLI. Its own ctr tool is for debugging, not daily use. nerdctl fills that gap with a Docker-compatible CLI, including nerdctl compose, optional rootless mode, lazy pulling and image signing support. It is a non-core subproject of containerd. Using containerd directly makes sense when you want the same runtime on laptops, servers and Kubernetes nodes, or when you need its newer features before Docker exposes them.

runc, crun and CRI-O

Below all three engines sits an OCI runtime, the program that actually creates namespaces and cgroups and starts the process. runc (Apache 2.0, written in Go) is the reference implementation and the default for Docker Engine and containerd. crun (GPL 2.0, written in C) comes from the same community as Podman and describes itself as faster and lighter on memory. Most engines let you choose either.

CRI-O is a Kubernetes-only runtime that implements the CRI and nothing else. It has no general CLI for developers. If your cluster runs CRI-O, you debug with crictl, not with Docker or Podman.

Side by side

Licence, ownership and support

  • Docker Engine: Apache 2.0, developed in the Moby project. Detailed release notes, with patch releases every week or two in 2026.
  • Docker Desktop: proprietary, under the Docker Subscription Service Agreement. Free only within the limits on the licence page (fewer than 250 employees and less than 10 million US dollars annual revenue, personal use, education, non-commercial open source). It bundles Docker Engine, the CLI, Compose, BuildKit and a VM.
  • Podman: Apache 2.0, part of the CNCF Sandbox project “Podman Container Tools” (accepted 21 January 2025). Two lines are patched today (6.1 and 5.8).
  • containerd and nerdctl: Apache 2.0, CNCF graduated (accepted 29 March 2017, graduated 28 February 2019). A written release policy with LTS dates.
  • runc: Apache 2.0, under the Open Container Initiative, with a written support policy for the last few minor lines. crun: GPL 2.0.

The licence story is simple once you separate the products. On Linux servers and CI runners, all three engines are free open source software. The commercial question only appears with Docker Desktop on laptops, and it has several free alternatives.

Running services: systemd, restart policies and Quadlet

  • Docker Engine restarts containers itself with --restart policies, and Compose projects come back after reboot if their containers had a restart policy. systemd only sees docker.service, so per-application logs, dependencies and limits live inside Docker.
  • Podman expects systemd to supervise containers. A Quadlet .container file in /etc/containers/systemd/ (system) or ~/.config/containers/systemd/ (user) becomes a normal service after systemctl daemon-reload. You get systemctl status, journalctl -u, ordering with other units and podman auto-update driven by a label. For rootless services that must survive logout, enable lingering for the user with loginctl enable-linger.
  • containerd with nerdctl supports restart policies through a small restart manager. For services on plain VMs, most teams pair it with systemd units or use Kubernetes instead.

Compose support

  • Docker Engine has the reference implementation, Docker Compose v5, as a CLI plugin.
  • Podman offers podman compose, which the documentation describes as a thin wrapper that runs an external provider: docker-compose if installed, otherwise podman-compose (1.6.0, June 2026). Test your own Compose files against it before relying on it, and consider converting long-lived services into Quadlet units.
  • nerdctl has nerdctl compose built in, covering the common parts of the Compose specification. Check the project’s list of unsupported keys before migrating a large file.

Networking and firewalls

  • Docker Engine manages iptables rules by default, and since 29.0 can use nftables as an experimental firewall-backend option. Published ports are opened by Docker’s own rules, so read Docker’s packet filtering and firewall documentation before you rely on host firewall tools. Publish to 127.0.0.1 when a reverse proxy sits on the same host.
  • Podman 6 uses Netavark with nftables for rootful networking and pasta for rootless. CNI and iptables are gone. Network isolation between networks is now the default.
  • containerd with nerdctl uses CNI plugins, with bypass4netns as an optional speed-up for rootless networking.

Remote access and socket security

  • Docker Engine: a Unix socket owned by root and the docker group by default. Docker documents SSH and TLS for remote access; a docker context with an ssh:// host is the simplest safe option. Never expose the API on TCP without TLS.
  • Podman: remote clients connect over SSH to a user’s or root’s Podman service (podman system connection). No TCP listener is needed.
  • containerd: the gRPC socket is local; remote use usually goes through SSH or Kubernetes.

Whatever the engine, mounting its socket into a container hands that container the engine’s full power.

Images, storage and build

All three pull and run the same OCI images from the same registries. Docker Engine 29 uses containerd’s content store on fresh installs, so images, layers and snapshotters behave much like containerd’s. Podman uses its own storage library (overlay by default) shared with Buildah and Skopeo. Builds go through BuildKit for Docker and nerdctl, and through Buildah for Podman; the image builds post covers that in depth. For where images live and how you trust them, see container registries with Harbor, Zot and CNCF Distribution and image signing with cosign, Notation and in-toto.

Laptops: macOS and Windows

Containers need a Linux kernel, so on macOS and Windows every option runs a Linux VM:

  • Docker Desktop: the most polished, with file sharing, networking, Kubernetes, extensions and enterprise controls such as sign-in enforcement. Subject to the licence terms above.
  • Podman Desktop with podman machine: free, Apache 2.0, with podman machine providers for Apple’s virtualisation framework, libkrun, Hyper-V and WSL. Podman 6 no longer supports Intel Macs or Windows 10, which matters for older machines.
  • Lima (Apache 2.0, 2.2.1 on 3 October 2026) and Colima (MIT, 0.10.3 on 4 June 2026) on macOS: lightweight VMs that can run Docker Engine or containerd with nerdctl, driven from the command line.

On Linux laptops, any engine runs natively. Rootless Podman or rootless Docker are both reasonable defaults.

Performance

There is little independent data comparing engines, and we ran no benchmark for this post. A few points:

  • Once a container is running, its processes run directly on the host kernel under namespaces and cgroups in every engine. CPU and memory speed inside the container do not depend much on whether Docker, Podman or containerd started it.
  • Differences show up in start time, image pull and unpack, and networking. Rootless networking through a user-mode stack is slower than a rootful bridge for heavy traffic; containerd and Docker support lazy pulling with snapshotters, which can shorten start times for large images.
  • crun’s README, in the author’s own run on their machine, reports 100 sequential /bin/true containers taking 1.69 seconds with crun against 3.34 seconds with runc. That is the project’s own measurement of runtime start-up only, not of an engine, and the README does not describe the machine.

If start-up speed or pull time matters for your CI, measure it on your own runners with your own images.

Operations and monitoring

  • Docker Engine: docker info, docker system df, docker events, log drivers and daemon.json. One service to watch.
  • Podman: podman info, podman events, journald logs per Quadlet service and systemctl for state. Many small services, each visible to normal Linux tools.

Fit with Kubernetes

  • Kubernetes nodes should run containerd or CRI-O. Docker Engine only works through the separately maintained cri-dockerd adapter, which Docker and Mirantis took over after dockershim was removed in Kubernetes 1.24.
  • On a node, use crictl for pods and containers. nerdctl --namespace k8s.io can show containerd’s view, but changing things behind the kubelet’s back causes confusion.
  • Locally, Podman’s kube play runs simple Kubernetes YAML without a cluster, and kind, k3d and minikube run small clusters on Docker or Podman. See the lightweight Kubernetes post for small real clusters.

What actually happens when you type run

With Docker Engine, docker run sends an API request over the socket to dockerd. The daemon makes sure the image is in containerd’s store, prepares a snapshot for the container’s filesystem, sets up the network, and asks containerd to create a task. containerd starts a shim, the shim calls runc with an OCI bundle (a root filesystem and a config.json), runc creates namespaces and cgroups, and the container’s first process starts. runc then exits; the shim stays as the parent.

With Podman, podman run does the image, storage and network steps itself as the calling user, writes the OCI bundle, starts conmon and calls crun or runc. There is no request to a daemon. With nerdctl, the CLI talks to containerd directly, and containerd does the rest just as it does for Docker.

Every path ends at the same kernel features and an OCI runtime. What differs is who holds power in the middle, and who can reach it.

A worked plan for the Jabalpur team

Step 1: Inventory machines, engines and access

List every VM, runner, laptop group and cluster node with its engine and version, who is in the docker group, which sockets are mounted into containers, whether any TCP API port is open, and which applications run through Compose files or docker run commands. Copy every Compose file and wiki command into a Git repository.

Step 2: Patch and close the obvious holes this week

Before choosing anything new, upgrade Docker Engine on the VMs to 29.9.2 from Docker’s official repository, one VM at a time, after reading the 29.0.0 breaking changes. Existing installs keep their current image store unless you migrate, so plan that separately. Remove people from the docker group, use sudo docker with logging for the rare manual task, or set up docker context over SSH for a named deploy user. Confirm no daemon listens on TCP.

Step 3: Fix the CI runners

Remove the socket mount from pipeline jobs. Rebuild each runner so that every job runs in a rootless engine owned by an unprivileged runner user: either rootless Podman with Buildah for builds, or rootless Docker with BuildKit. Pushes use a short-lived registry token per pipeline, not a shared login. Jobs from untrusted branches run on a separate runner with no deploy credentials. If a job truly needs privileged access, it gets its own throwaway VM.

Step 4: Choose the server path

  • Path A: stay on Docker Engine 29 with Compose files in Git, deployed by CI over an SSH context, restart policies for each service, and published ports bound to 127.0.0.1 behind the reverse proxy. Smallest change; everybody already knows the commands.
  • Path B: move services to rootful or rootless Podman with Quadlet. Each service becomes a .container file in Git, deployed by configuration management, supervised by systemd, logging to journald and updated with podman auto-update. Better fit with Linux tools and no central daemon, but every Compose file must be converted and the team must learn Quadlet.

For Jabalpur, with fourteen VMs, mostly Compose-based applications and a busy platform team, Path A first is the sensible choice: it fixes the reviewer’s findings within weeks. They will trial Path B on two new internal services this quarter, and decide in six months whether to move more.

Step 5: Settle laptops and the licence

Count who really uses Docker Desktop features (Kubernetes in Desktop, extensions, Windows containers, enterprise controls). Buy subscriptions for that group, since the company is beyond the free limits. Offer Podman Desktop, or Colima on macOS, to everyone else, with a short internal guide and a test that the team’s main Compose file works. Linux laptops move to rootless Podman or rootless Docker. Agree on images and Compose files, not on one engine for everyone.

Step 6: Keep Kubernetes nodes on containerd

The k3s cluster keeps its bundled containerd. Add containerd version checks to the cluster upgrade checklist, since most 2026 containerd advisories were in the CRI plugin. Teach on-call engineers crictl, and stop the habit of installing Docker Engine on nodes “for debugging”.

Step 7: Set image and runtime rules

Pull only from the company registry or approved upstream registries. Pin base images by digest in production. Turn off features nobody uses: no checkpoint and restore, no AuthZ plugins unless maintained, no experimental flags on production hosts. Keep runc or crun current through the engine’s packages.

Step 8: Monitor, patch and review

Subscribe to the Moby, Podman, containerd, runc and crun security advisories on GitHub. Patch container engines within a week of a high or critical fix. Add a weekly report of engine versions across machines. Review docker group membership and socket mounts every quarter.

When Docker Engine is the better choice

  • Your applications are already described in Compose files and your team knows Docker well.
  • Vendor install guides, CI templates or tools you depend on assume a Docker API and behave best with the real thing.
  • You use Swarm mode for simple multi-host services and it meets your needs.
  • You can run it rootless on runners and laptops, or keep the rootful socket tightly controlled on servers.

When Podman is the better choice

  • You want containers as normal systemd services, with journald logs and unit ordering, and no central daemon.
  • You run on Fedora, RHEL or a family member where Podman is the packaged and supported engine.
  • Rootless by default matters, for example on shared build machines or developer laptops.
  • You want pods and kube play to bring local work closer to Kubernetes.
  • You need a free desktop option and your laptops meet Podman 6’s platform requirements.

When containerd with nerdctl is the better choice

  • You want the same runtime on laptops, VMs and Kubernetes nodes.
  • You need newer containerd features, such as particular snapshotters or lazy pulling, without waiting for Docker to expose them.
  • You are building a platform or appliance and want a small, embeddable runtime with a clear LTS policy.

When not to change at all

If your engines run supported versions and are patched within days, nobody has silent root through a group or socket, CI jobs run rootless or on throwaway machines, laptops are licensed correctly and your services restart cleanly after a reboot, there is no good reason to change engines in 2026. Spend the time on image hygiene and monitoring. Switching engines on servers touches every application at once, and should solve a problem you can name.

A short history, so the choices make sense

  • March 2013: at PyCon US on 15 March, Solomon Hykes of dotCloud, a platform-as-a-service company, shows Docker in public for the first time in a lightning talk.
  • 2015: runc’s repository starts in June as the reference runtime of the Open Container Initiative, and containerd’s first commit lands in July.
  • April 2016: Docker 1.11 is the first Docker release built on containerd and runc.
  • 2017: containerd joins the CNCF (29 March), and on 18 April Docker announces the Moby Project at DockerCon as the open source home of Docker’s components. Podman’s repository starts in November 2017.
  • January 2019: Podman 1.0 is released (11 January), and containerd graduates in the CNCF (28 February).
  • 2019 to 2023: CRI-O joins the CNCF (April 2019) and graduates (July 2023).
  • May 2022: Kubernetes 1.24 removes dockershim.
  • February 2023: Podman 4.4 introduces Quadlet.
  • 2025: Podman Container Tools joins the CNCF Sandbox (January); Docker Engine 29.0 (November) makes the containerd image store the default; Compose 5.0 follows in December.
  • 2026: containerd 2.3 LTS (April) and 2.4 (September); Podman 6.0 (June) drops cgroup v1, iptables and CNI; runc 1.5 (June); a year of security releases across every project.

Operational costs to plan for

  • Patching time. Docker Engine had patch releases every one to two weeks in 2026, containerd several security releases, Podman two active lines. Plan a monthly engine patch window and an emergency one.
  • Upgrade work. Docker Engine 29.0 and Podman 6.0 both had breaking changes. Budget testing time for major releases, especially around networking (nftables, Netavark) and storage (containerd image store, SQLite migration).
  • Rootless setup. Subordinate ID ranges, cgroup delegation, lingering users, storage in home directories and port limits. Write it down once in configuration management.
  • Licences. Docker Desktop subscriptions for the people who need them, reviewed yearly as headcount changes.
  • Learning time. Quadlet for Podman, crictl for nodes, contexts and rootless mode for Docker. Practise before an incident forces it.
  • Monitoring. Engine versions, disk usage by images and volumes, restart loops and failed health checks on every host.

A decision guide

  • Compose-based applications on a few VMs, team knows Docker: Docker Engine 29, patched, with SSH contexts and no docker group for humans.
  • Services that should be native systemd units on RHEL-family or Fedora hosts: Podman 6.1 with Quadlet.
  • CI runners: rootless Podman with Buildah, or rootless Docker with BuildKit, with no socket mounts; throwaway VMs for privileged jobs.
  • Kubernetes nodes: containerd 2.3 LTS or 2.4, or CRI-O, matching your Kubernetes version.
  • One runtime everywhere, platform team comfortable with lower-level tools: containerd with nerdctl.
  • Laptops in an organisation beyond the free limits: Docker Desktop with a subscription for those who need it, Podman Desktop or Colima for others.
  • A supported, patched setup with controlled access: stay, and improve monitoring.

Checklist before you install another container daemon

  • Every machine’s engine and version is listed, and every version is supported.
  • Engine security releases are applied within a week of a high or critical advisory.
  • Nobody is in the docker group by habit; access goes through sudo with logging or SSH contexts for named users.
  • No engine API listens on TCP without TLS, and none listens on TCP at all if SSH would do.
  • No CI job or application container has the engine socket mounted.
  • CI jobs run rootless or on throwaway VMs, and untrusted branches never see deploy credentials.
  • Compose files and Quadlet units live in Git and are deployed by a pipeline.
  • Published ports are bound to 127.0.0.1 when a local reverse proxy fronts them.
  • Hosts run cgroup v2 and are ready for nftables.
  • Production images are pinned by digest and come from approved registries.
  • Unused features such as checkpoint restore, AuthZ plugins and experimental flags are off.
  • Laptop tools are licensed for your organisation’s size.
  • Kubernetes nodes run containerd or CRI-O, and on-call engineers know crictl.

Common mistakes

  • Treating the docker group as a convenience. It is root on that machine.
  • Mounting the socket into CI jobs. Any pipeline step, or any dependency it runs, then controls the host.
  • Thinking Podman’s socket is safe to mount. A rootful Podman API socket gives the same power as Docker’s.
  • Confusing Docker Desktop and Docker Engine licences. Desktop on laptops may need a subscription; Engine on Linux servers is Apache 2.0.
  • Upgrading across a major release without reading the notes. Podman 6.0 removes CNI, iptables and cgroup v1 support; Docker 29 changes the default image store and minimum API version.
  • Installing Docker Engine on Kubernetes nodes “to help”. It adds a second runtime, a second image store and confusion.
  • Running everything rootless without planning storage and ports. Images fill home directories, and low ports need extra setup.
  • Ignoring runc and crun. They have their own advisories; make sure your engine packages bring current versions.

A small hands-on drill

No hands-on drill was run for this post on our shared test machine. We did not install any engine, start any daemon or change any system setting; every fact above comes from the release notes, documentation and advisories in the sources. To see the differences yourself, try this on your own throwaway Linux VM, never on a production host. It takes about 20 minutes.

  1. See who holds power. With Docker Engine installed, run ls -l /var/run/docker.sock and getent group docker. Every user listed can control a root daemon. Run docker info --format '{{.SecurityOptions}}' and note whether rootless appears.
  2. Watch the process tree. Start docker run -d --name demo docker.io/library/nginx:stable and look at ps -ef --forest: the container’s processes hang under a containerd shim, not under your shell. Then, as a normal user with Podman installed, run podman run -d --name demo docker.io/library/nginx:stable and check the tree again: you will find conmon owned by your user, and no central daemon.
  3. Check what root means inside. Run podman run --rm docker.io/library/alpine cat /proc/self/uid_map as a normal user. The mapping shows that UID 0 inside the container is your own UID on the host. Run podman info --format '{{.Host.Security.Rootless}}' to confirm.
  4. Turn a container into a service. Create ~/.config/containers/systemd/demo-web.container with a [Container] section containing Image=docker.io/library/nginx:stable and PublishPort=127.0.0.1:8080:80, a [Service] section with Restart=on-failure, and an [Install] section with WantedBy=default.target. Run systemctl --user daemon-reload, then systemctl --user start demo-web and journalctl --user -u demo-web. That is Quadlet in practice.
  5. Try remote access the safe way. From another machine, run docker context create vm1 --docker "host=ssh://youruser@vm1" and docker --context vm1 ps. No TCP port was opened.
  6. Clean up. Remove the containers, the Quadlet file and the context, and delete the VM.

Note what surprised you; for most people it is step 1.

Unlearn, re-learn

Unlearn the idea that a container engine is just a tool you install once and forget, and the habit of adding people to the docker group or mounting the socket because it saves a few keystrokes. Unlearn, too, the idea that switching engines fixes a security finding by itself. Re-learn containers as processes with a clear owner: a root daemon you guard carefully, a rootless engine owned by one user, or systemd units you can see with normal Linux tools. Practise it by listing who can reach each engine socket this week, removing one socket mount from CI, and patching your engines before the next advisory lands.

Sources

comments powered by Disqus

Releted Posts

TLS certificate automation in 2026: revisit cert-manager, Certbot and step-ca before the next certificate expires on you

Think of a bicycle and auto parts manufacturer in Ludhiana that sells through about 400 dealers across Punjab, Haryana, Rajasthan and the hill states (an invented example, not a real client).

Read more

Vulnerability scanners in 2026: revisit Trivy, Grype and OSV-Scanner before you trust another green scan

Picture a small software company in Bhubaneswar that builds claims and pre-authorisation software for hospitals that deal with health insurance TPAs (an invented example, not a real client).

Read more

Lightweight Kubernetes in 2026: revisit k3s, k0s and Talos Linux before you stand up another small cluster

Picture a software company in Visakhapatnam that builds gate and yard software for container freight stations around the Visakhapatnam and Gangavaram ports, with two more customers at Kakinada and one inland depot near Vizianagaram (an invented example, not a real client).

Read more