18 Months After Ditching Docker, and It Took Reading the runc Advisory All the Way Through

·Platform Decision·11 min read

Translated from the original Korean post. 한국어 원문 보기 →

Last November 5th, morning. Someone dropped a link in our internal security channel: three runc advisories. CVE-2025-31133, CVE-2025-52565, CVE-2025-52881. All three land in the same place — the attacker gets a read/write path into the host's /proc.

My first reaction was relief. Smugness, honestly. We'd ripped out Docker 18 months earlier. Podman on every build host, rootless wherever we could manage it, no root-owned daemon socket sitting around like an unlocked door. At the 11 a.m. standup I said "we're not affected." It took me two hours to find out I was wrong.

I read the advisories all the way through. The runc maintainers had noted that crun and youki were likely to have the same class of issue, and that patches were shipping together. On Fedora hosts, Podman calls crun. I'd swapped out the layer directly above the vulnerability and quietly assumed the layer beneath it had been fixed along with it. What I got out of that day was the discovery that the assumption had nothing behind it.

We patched every host that week. Same schedule as the teams still running Docker. Same mood, too.

So why is everyone migrating

The exodus is real. The reason is boring. It's money.

Docker Desktop is free for personal use, education, non-commercial open source, and companies under 250 employees with under $10M in annual revenue. Cross any one of those lines and you're buying a license for every developer. As of August 2026 the official price list puts Pro at $9 per user/month billed annually ($11 monthly), Team at $15 ($16 monthly), and Business at $24. A 60-engineer org on Business is looking at a little over $17,000 a year for one local dev tool.

The first calculation I ran when I saw that number was pretty domestic. $17,000 buys how many more CI runners? How far does it go toward a headcount? The conclusion arrives before the arithmetic finishes. This kind of thing gets moved by procurement, not security.

One point that trips people up constantly: this is about the Docker Desktop license, not Docker itself. Docker Engine on Linux is still Apache 2.0. If your CI runners sit on Linux, that was never what you were paying for.

What actually gets replaced

The phrase "Docker replacement" hides how thick Docker is. Top to bottom, roughly: the docker CLI, Compose, BuildKit, the dockerd daemon, containerd, and the OCI runtime — runc or crun.

Nearly every tool on the candidate list swaps the top four and leaves the bottom two alone. If someone had put that one sentence in front of me in 2024, I'd have saved a quarter, not an afternoon.

Docker is walking toward the same floor. Docker Engine v29 defaults to the containerd image store on fresh installs and has retired the old graph drivers entirely. Everyone who was fighting at the top meets at the bottom.

Audit before you move

Before anybody claims a migration changed their security posture, here's the script we run now.

#!/usr/bin/env bash
# runtime-audit.sh — 이 컨테이너를 실제로 돌리는 건 무엇인가
# bash 5.x 기준. Docker Engine 29.6.2, Podman 6.0.2,
# nerdctl 2.3.0 (Ubuntu 24.04)에서 확인.
set -uo pipefail

row() { printf '%-10s %-14s %s\n' "$1" "$2" "$3"; }

row TOOL VERSION "LOW-LEVEL RUNTIME"
printf '%.0s-' {1..50}; echo

if command -v docker >/dev/null 2>&1; then
  row docker \
    "$(docker version --format '{{.Server.Version}}' 2>/dev/null || echo unavailable)" \
    "$(docker info --format '{{.DefaultRuntime}}' 2>/dev/null || echo unavailable)"
fi

if command -v podman >/dev/null 2>&1; then
  row podman \
    "$(podman version --format '{{.Client.Version}}' 2>/dev/null || echo unavailable)" \
    "$(podman info --format '{{.Host.OCIRuntime.Name}}' 2>/dev/null || echo unavailable)"
fi

if command -v nerdctl >/dev/null 2>&1; then
  row nerdctl \
    "$(nerdctl version --format '{{.Client.Version}}' 2>/dev/null || echo unavailable)" \
    "set in /etc/containerd/config.toml"
fi

echo
echo "runtime binaries on this host:"
for rt in runc crun youki; do
  if command -v "$rt" >/dev/null 2>&1; then
    printf '  %-6s %s\n' "$rt" "$("$rt" --version 2>/dev/null | head -n1)"
  fi
done

On a containerd host the whole argument comes down to one config line. This fragment is from the official CRI config guide (containerd 2.x).

version = 3

[plugins."io.containerd.cri.v1.runtime".containerd]
  default_runtime_name = "crun"

[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.crun]
  runtime_type = "io.containerd.runc.v2"

[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.crun.options]
  BinaryName = "/usr/local/bin/crun"

That BinaryName line is what actually moves your containers. Everything above it is muscle memory, packaging, and licensing.

Six candidates

후보 최신 라이선스 걸리는 지점
Podman 6.0 (2026-06-24) Apache 2.0 6.0 drops CNI and cgroups v1
containerd + nerdctl nerdctl 2.3.0 (2026-05) Apache 2.0 Batteries not included; assembly required
Apple container 1.0.0 (2026-06-09) Apache 2.0 Apple Silicon + macOS 26 only
OrbStack Commercial Closed source, $8/month macOS only; trading one license for another
Rancher Desktop / Colima 1.23.1 (2026-07) / 0.10.0 (2026-02) Open source Wrappers; debugging drops to the layer below
Buildah / BuildKit — Open source Doesn't solve the desktop problem

Podman 6.0

The default answer handed to any team pulling Docker Desktop over licensing. 6.0 landed June 24, 2026.

The good. No long-lived root daemon. Rootless is how it works, not a tuning knob you get to later. Quadlet turns containers into real systemd units, which matters more than you'd expect if you already manage systemd — one unit file and restarts, dependencies, and logs go back where you keep everything else. It also exposes a Docker-compatible API socket, so docker compose runs untouched.

# Podman 6.x, systemd 호스트에서 루트리스로.
systemctl --user enable --now podman.socket
export DOCKER_HOST="unix://${XDG_RUNTIME_DIR}/podman/podman.sock"
loginctl enable-linger "$USER"   # 로그아웃해도 소켓 유지
docker compose up -d

The bad. 6 is a big cleanup release. CNI, iptables, slirp4netns, cgroups v1, BoltDB, Intel Macs, and Windows 10 all came out at once. The nastiest part is that leftover CNI config gets silently ignored rather than erroring. Containers come up fine. There's just no network. Hit that symptom at 3 a.m. and you'll burn an hour finding it. The compat layer targets the Docker v1.40 API, so odd Compose features can drift.

Pick it if you're on Linux with cgroups v2 and systemd. Sit this one out and stay on 5.x if you're on CNI or cgroups v1.

containerd + nerdctl

The honest choice is bringing what you already run in production down into dev. nerdctl 2.3.0 shipped May 2026.

containerd is the thing EKS, GKE, and AKS run. nerdctl feels close enough to the Docker CLI that the adjustment cost is low, and it comes with Compose, rootless, and lazy-pulling snapshotters. What it doesn't come with is batteries. You stand up CNI, BuildKit, and snapshotter config yourself.

containerd has its own CVEs too. June 2026 patched five CRI plugin vulnerabilities across 1.7.33, 2.0.10, 2.1.9, 2.2.5, and 2.3.2 — one of them an 8.3 where a LABEL in an image could get host commands executed. Going lower in the stack doesn't make it quieter.

Pick it if closing the dev/prod gap is the goal. Skip it if nobody on the team wants to assemble infrastructure by hand.

Two roads on the Mac

Apple's container is genuinely new. 1.0.0 shipped June 9, 2026 under Apache 2.0, exactly a year after the WWDC 2025 preview. Each container gets its own lightweight VM via Virtualization.framework, so workloads don't share a kernel. From a security seat, that isolation boundary is a qualitatively different thing than namespaces. Bidirectional OCI compatibility, no idle daemon, and 1.0 added container machine for a persistent Linux environment. It only runs on Apple Silicon and macOS 26, though. That's less a constraint than a wall. The ecosystem is one year old and the Linux CI side still needs work.

OrbStack is the pragmatic Mac choice and also the one people grumble about under their breath. It runs real Docker Engine and Compose, so nothing about your workflow changes. In my own use, startup time and file-sharing performance on Apple Silicon were noticeably better than Docker Desktop. I won't publish exact benchmark numbers, but the gap was obvious enough that nobody wanted to go back. The catch is that it's a license traded for a license. Free for personal non-commercial use only, capped at $10K in related annual revenue. Commercial use is $8 per user/month. macOS only, source closed. It's a slot where the math only has to show $8 beats $24 — and if "must be open source" is written into your requirements, it's out on the spot.

There's a free middle ground too. Rancher Desktop 1.23.1 landed July 2026, Colima 0.10.0 in February 2026. Both are open source and free for commercial use, and Rancher Desktop bundles k3s so you get a single-node cluster without a separate tool. It also has a switch between containerd and dockerd. Colima is a lean, scriptable CLI on top of Lima. Both are wrappers, though. When something breaks, the bug is usually in Lima or containerd or the VM layer, and debugging keeps descending. Rancher Desktop is Electron-based with the heft that implies, and Colima has no GUI, so you get friendly with config files.

CI is a completely different problem

Half of "we need a Docker alternative" is actually "we can't run privileged Docker-in-Docker on Kubernetes runners." That's not a desktop conversation.

Buildah and BuildKit both bake OCI images without a privileged daemon. Buildah pairs well with Podman and builds rootless. BuildKit is the engine docker buildx already uses, so cache semantics and multi-platform builds come along unchanged.

The thing to actually worry about here isn't performance, it's maintenance status. Google archived Kaniko on June 3, 2025. Until Chainguard forked it, a tool holding up pipelines across regulated industries was just sitting there unmaintained. Picking an unmaintained builder is a slow-motion CVE incident. The date it goes off just hasn't been set yet.

Where this advice doesn't apply

Under 250 employees and under $10M revenue, Docker Desktop is free. You don't have to do this migration. That time and money is better spent elsewhere.

If your team is a mix of Mac and Windows and Linux, OrbStack and Apple container drop off the list immediately. Standardization beats tool performance. That's my bias, but I've noticed anyone who's debugged a 3 a.m. outage in an org where every dev environment is different tends to end up with the same one.

If you lean on TestContainers or unusual Compose features, budget real time for compatibility testing. The API compat layers are good but not perfect. I watched a team burn two weeks on one Compose edge case. To save $9 per person. Nobody did the math on two weeks of salary.

If security is your top priority, changing the runtime's name changes almost nothing. Docker Desktop had a serious container escape too — CVE-2025-9074, CVSS 9.3, patched in 4.44.3 in August 2025. So did runc, crun, and containerd. Rootless and VM boundaries are real improvements. Swapping CLIs isn't.

The order I keep now

Audit before you move. Run docker info --format '{{.DefaultRuntime}}' and podman info --format '{{.Host.OCIRuntime.Name}}' side by side, and if both spit out a runc-family binary, this move is a licensing move, not a security move. I don't mix licensing and security in the same document. Docker Desktop pricing is a procurement problem with a known answer; container escape is a structural problem that a CLI swap doesn't solve.

On Linux, Podman is the safe default, but 6.0 is a breaking upgrade, so comb through CNI config before you roll it out. containerd plus nerdctl gets dev and prod closest together, and the price is standing up the infrastructure yourself. Isolation comes from boundaries, not tool names: rootless, user namespaces, or per-container VMs like Apple container. And check the maintenance status of everything sitting on your build path. Kaniko looked fine right up until it didn't.

That sentence I said at standup last November — "we're not affected" — still comes back to me sometimes. Would I have finished reading the advisory sooner if I hadn't said it? I don't know. Still don't.

Was this post helpful?

One click helps me write the next one

#Docker#Podman#containerd#Container Security#runc