Docker is no longer the only game in town. Podman matured dramatically, Lima made macOS containers practical, and containerd became the standard for production. In 2026, choosing a container runtime requires actually understanding your options. Here's the honest breakdown.
The Docker Monopoly Is Over #
Docker's dominance was always about convenience, not technical superiority. The Docker daemon (dockerd) that runs as root, the proprietary CLI, and the closed ecosystem were always compromises. In 2026, the alternatives are production-ready for most use cases.
Podman: Docker-Compatible Without the Daemon #
Podman became the default for security-conscious teams. No daemon means no root privileges, no daemon crashes, and better systemd integration.
Installation and Setup
brew install podman
podman machine init
podman machine start
sudo dnf install podman
podman run --rm docker.io/library/alpine echo "Podman works!"
The Daemon-Free Architecture
Pods: Kubernetes-Style Grouping
podman pod create --name myapp-pod \
-p 8080:80 \
-p 5432:5432
podman run -d --pod myapp-pod --name nginx nginx:alpine
podman run -d --pod myapp-pod --name postgres postgres:16
podman generate kube myapp-pod > myapp.yaml
Rootless Containers
$ podman run --rm alpine id
uid=0(root) gid=0(root)
podman unshare cat /proc/self/uid_map
Dockerfile Compatibility
podman build -t myapp:latest .
podman push myapp:latest docker://registry.example.com/myapp:latest
podman push myapp:latest containers://registry.example.com/myapp:latest
Lima: macOS Containers That Actually Work #
Docker Desktop on macOS was always a compromise: a full Linux VM running Docker. Lima gives you the same result with less overhead.
The Problem with Docker Desktop on macOS
Setting Up Lima
brew install lima
limactl start
limactl shell default docker build -t myapp .
limactl shell default docker run -p 8080:80 myapp
Custom Lima Configuration
images:
- location: "https://deps.sh/lima/alpine/3.19.1/lima.yaml"
arch: "x86_64"
- location: "https://deps.sh/lima/alpine/3.19.1/lima.yaml"
arch: "aarch64"
provision:
- mode: system
script: |
apk add --no-cache \
containerd \
docker \
docker-cli-compose \
buildkit
- mode: user
script: |
systemctl --user enable containerd
systemctl --user start containerd
provision_scripts:
- mode: system
script: |
cat > /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": ["https://mirror.gcr.io"],
"storage-driver": "overlay2"
}
EOF
rc-service docker start
mounts:
- location: "~"
writable: true
- location: "/tmp/lima"
writable: true
networks:
- lima: bridged
cpu: 4
memory: 8GB
disk: 100GB
containerd: The Standard for Production #
containerd is what runs inside Docker and Kubernetes. You can use it directly for simpler, more secure deployments.
Why Use containerd Directly
Using ctr (containerd CLI)
apt install containerd
ctr images pull docker.io/library/nginx:alpine
ctr images ls
ctr run -t --rm docker.io/library/alpine:latest test-container ash
ctr ns ls
ctr -n k8s.io containers ls
nerdctl: Docker-Compatible CLI for containerd
brew install nerdctl
nerdctl build -t myapp:latest .
nerdctl run -p 8080:80 myapp:latest
nerdctl compose up
BuildKit: Faster Builds with Cache Mounts #
BuildKit is the modern builder for Docker/Podman/containerd. It handles concurrent builds, better caching, and more efficient layer management.
BuildKit.toml
[registry."docker.io"]
mirrors = ["registry.docker.io"]
[registry."gcr.io"]
insecure = true # For air-gapped environments
[worker.oci]
max-parallelism = 4 # Limit concurrent builds
[driver]
snapshotter = "overlayfs" # Faster than native
Build Commands with Cache
docker build --build-arg BUILDKIT_INLINE_CACHE=1 -t myapp:latest .
docker build -t myapp:latest . <<'EOF'
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --only=production
COPY . .
EOF
Multi-Platform Builds
docker buildx create --use
docker buildx inspect --bootstrap
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag myapp:latest \
--push \
.
Kubernetes with containerd #
version = 2
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/:3.9"
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
snapshotter = "overlayfs"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
privileged_without_host_devices = false
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = "/usr/bin/runc"
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
The Decision Framework #
| Tool | Best For | Installation | Complexity |
|---|---|---|---|
| Docker | Beginners, cross-platform dev | Easy | Low |
| Podman | Security-conscious, Linux dev | Easy | Medium |
| Lima | macOS users who want performance | Easy | Medium |
| containerd | Production K8s nodes | Manual | High |
The Cost of Docker Desktop #
The Bottom Line #
Docker isn't going away — it's still the most compatible and well-documented option. But in 2026, you have real choices:
macOS users: Try OrbStack or Lima before Docker Desktop - Security-conscious teams: Podman is now production-ready - Kubernetes users: You already use containerd; consider using it directly - Everyone else: Docker still works fine
The days of "Docker is containers" are over. Containers are infrastructure, and infrastructure deserves thoughtful choices.
Using an alternative to Docker in 2026? What's your setup?