Your laptop fan spins up the moment you open Docker Desktop. Memory climbs past 2 GB before you've started a single container. And then there's that pop-up: "Docker Desktop requires a paid subscription for your organization."
That's the moment most developers start Googling. The good news: the ecosystem has matured enough that switching is no longer a compromise.
Quick Comparison: Which Tool for Which Situation
| Tool | macOS/Win GUI | Linux | Free (Commercial) | Kubernetes | Rootless | Docker Socket |
|---|---|---|---|---|---|---|
| Docker Desktop | Yes | No | No (>250 emp.) | Yes | No | Yes |
| Rancher Desktop | Yes | No | Yes | Yes | Yes | Yes |
| OrbStack | Yes | No | No ($8/mo) | Yes | Yes | Yes |
| Podman | CLI only | Yes | Yes | Via pods | Yes | With config |
| nerdctl + containerd | CLI only | Yes | Yes | No | Yes | No |
| CRI-O | No | Yes | Yes | Yes (only) | Yes | No |
- If you're on macOS and want a free Docker Desktop replacement: Rancher Desktop.
- If you're on Linux or targeting production: Podman.
- If you're optimizing a CI/CD pipeline: Podman or nerdctl + containerd.
- If you just want the fastest local containers on a Mac: OrbStack (paid).
Why Developers Are Moving Away from Docker Desktop
Docker Desktop's licensing shift in January 2022 was the main trigger. Companies with more than 250 employees or over $10M in annual revenue now require a paid subscription. The CLI (docker on Linux) stays free; it's the GUI wrapper that became commercial.
But licensing isn't the only reason. Docker's daemon architecture has a structural problem: a single background service runs as root. One container breakout potentially compromises the entire host. Every alternative on this list was designed specifically to avoid this.
Podman: Daemonless, Rootless, Drop-In Compatible
Podman is Red Hat's answer to Docker's root daemon. The architecture difference is fundamental: instead of a central service, each container runs as a direct child process of your user account.
# Install on Fedora/RHEL
sudo dnf install podman
# Install on Ubuntu/Debian
sudo apt install podman
# macOS (via Homebrew)
brew install podman
podman machine init && podman machine startMost Docker commands work without any changes:
alias docker=podman
podman run -d --name web -p 8080:80 nginx
podman ps
podman build -t my-app .Where alias docker=podman actually breaks down
The single-alias migration works for the CLI, but three things commonly trip people up:
- Docker socket tools. Testcontainers, VS Code Dev Containers, and some CI runners look for
/var/run/docker.sock. Podman creates its own socket at/run/user/<UID>/podman/podman.sock. Fix:
# Enable Podman socket
systemctl --user enable --now podman.socket
# Point tools to it
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock- docker-compose. Podman doesn't include Compose natively. Options: install
podman-compose(community), or usepodman composewhich wraps the official Docker Compose binary if it's installed.
pip install podman-compose
podman-compose up -dFor most docker-compose.yml files this works without changes. The exception: services that use network_mode: host behave differently in rootless mode because the user namespace remaps ports below 1024.
- Dockerfile USER instructions. In rootless Podman,
USER rootinside a container maps to your unprivileged UID on the host, not actual root. This is by design (it's the security feature), but it can break build steps that assume real root access.
Pods: the Podman feature Docker doesn't have
Podman supports grouping containers into pods that share a network namespace, mirroring Kubernetes Pods directly. If you're developing for Kubernetes, this lets you test pod-level networking locally before pushing to a cluster:
podman pod create --name app-pod -p 8080:80
podman run -d --pod app-pod --name web nginx
podman run -d --pod app-pod --name sidecar my-proxyBest for: Linux development, production systems on RHEL/Fedora/CentOS, security-conscious teams, anyone targeting Kubernetes.
nerdctl + containerd: Docker CLI on a Leaner Runtime
containerd is Docker's runtime layer, extracted and donated to the CNCF. It now runs Kubernetes on GKE, EKS, and AKS. nerdctl is a Docker-compatible CLI that talks directly to containerd, bypassing Docker's overhead entirely.
nerdctl run -d --name web -p 8080:80 nginx
nerdctl ps
nerdctl build -t my-app .
nerdctl compose up -dSame commands, same flags. The practical advantage over Docker shows up in two areas:
Lazy pulling. nerdctl supports eStargz and IPFS-based image distribution. Containers can start before the image finishes downloading; only the layers needed for startup are pulled first. In a CI environment with large images, this alone can cut job startup time significantly.
CI/CD without privileged containers. Running Docker-in-Docker (DinD) requires --privileged mode, which grants the container nearly full host access. containerd-native CI runners (GitHub Actions with a containerd runner, GitLab with the containerd executor) skip this entirely.
Where nerdctl falls short
nerdctl doesn't expose a Docker-compatible socket. Tools that communicate via the Docker API (not just the CLI) won't work. If your toolchain depends on DOCKER_HOST or docker.sock for anything beyond basic CLI operations, Podman's socket compatibility is better suited.
Best for: Kubernetes shops, CI/CD pipelines, teams who want cutting-edge features (lazy pulling, image encryption) with Docker CLI familiarity.
Rancher Desktop: The Free Docker Desktop Replacement
For macOS and Windows developers who need a GUI, Rancher Desktop is the most complete free option. It bundles containerd (or dockerd), nerdctl, kubectl, and a full k3s Kubernetes cluster.
What makes it practical day-to-day:
- Runtime toggle between containerd and dockerd without reinstalling.
- Docker socket compatibility out of the box; Testcontainers and VS Code Dev Containers work immediately.
- Integrated
nerdctl composefor Compose workflows. - Built-in Kubernetes with one-click reset.
macOS local Kubernetes cluster setup:
# After installing Rancher Desktop, k3s is already running
kubectl get nodes
# NAME STATUS ROLES AGE
# lima-rancher-desktop Ready control-plane,master 2m
# Deploy locally
kubectl apply -f deployment.yaml
kubectl port-forward svc/my-service 8080:80No extra configuration. The cluster runs inside the same Lima VM as your containers, so images you build with nerdctl are immediately available to Kubernetes without a registry push.
Performance on macOS is noticeably better than Docker Desktop for most workloads. Rancher Desktop uses Lima, a lightweight VM layer; Docker Desktop uses its own virtualization stack. The difference shows most with I/O-heavy workloads and cold starts.
Best for: macOS/Windows developers who want a full local stack (containers + Kubernetes) for free.
OrbStack: macOS Performance Benchmark
OrbStack is not free for commercial use ($8/month after a 90-day trial), but it's become the performance reference point for local containers on macOS. Its VM layer is substantially lighter than both Docker Desktop and Rancher Desktop.
In practice this means:
- Cold start: OrbStack starts a container in roughly 1-2 seconds vs. 5-8 seconds for Docker Desktop on the same machine.
- Memory: baseline VM footprint is around 300-500 MB vs. 1.5-2 GB for Docker Desktop.
- File sync: I/O performance on mounted volumes is significantly faster, which matters for anything that reads/writes to the host filesystem frequently (like a dev server watching for file changes).
Full Docker socket compatibility, so existing tooling works without configuration.
Best for: macOS developers who do container-heavy work daily and want the fastest local experience. The pricing is straightforward relative to Docker Desktop.
CRI-O: Kubernetes Runtime, Not for Local Dev
CRI-O implements only the Kubernetes Container Runtime Interface. No CLI, no build tooling, no registry client; just the bridge between kubelet and the underlying runtime.
If you're managing Kubernetes clusters (OpenShift defaults to CRI-O), understanding it explains why prod behavior differs from local Docker. You wouldn't use it for local development. But if you're debugging why something runs differently on the cluster than on your machine, confirming the runtime version is a useful first step:
# On a CRI-O node
crictl info | grep -i versionCommon Migration Issues
Testcontainers with Podman
Testcontainers uses the Docker API by default. With Podman:
# In your test environment config or .testcontainers.properties
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
export TESTCONTAINERS_RYUK_DISABLED=trueThe Ryuk resource reaper doesn't work with rootless Podman's process model. Disable it and rely on manual cleanup or test framework teardown instead.
VS Code Dev Containers with Rancher Desktop
Rancher Desktop's Docker socket compatibility usually makes this transparent. If VS Code can't find the socket:
# Check where Rancher Desktop puts its socket
ls ~/.rd/docker.sock
# Add to VS Code settings.json
"docker.host": "unix:///Users/<you>/.rd/docker.sock"Networking: rootless containers and ports below 1024
Rootless containers (Podman, nerdctl in rootless mode) can't bind to ports below 1024 by default because that requires kernel capabilities. Common fix:
# Allow unprivileged port binding (Linux)
sudo sysctl net.ipv4.ip_unprivileged_port_start=80
# Or map to a high port and use a reverse proxy
podman run -p 8080:80 nginx
# then nginx/caddy on the host forwards :80 -> :8080Volumes and SELinux
On RHEL/Fedora with SELinux enforcing, bind mounts often fail silently. Add the :z or :Z label:
podman run -v /host/path:/container/path:z my-image
# :z = shared between containers
# :Z = private to this containerThis is one of the most common Podman gotchas that doesn't appear in Docker because Docker's daemon runs as root and bypasses SELinux context checks.