Living without docker: podman as a daily driver

Podman is a near drop-in replacement for the Docker CLI. Daemonless, rootless by default, ships in immutable distros. Muscle-memory translation and the few places the abstraction leaks.

Podman ships on most modern Linux desktops, Bazzite included. The CLI is intentionally docker-compatible (most subcommands and flags work identically), but two things differ under the hood:

  1. No daemon. Podman invokes containers directly via runc/crun. There's no long-running root service.
  2. Rootless by default. Each user has their own container store under ~/.local/share/containers/. Containers run mapped to your UID via user namespaces.

Both are a security win. They also remove the "is the daemon running?" and "is my user in the docker group?" failure modes.

# Common docker commands as podman commands

docker pull alpine          →   podman pull alpine
docker run -it alpine sh    →   podman run -it alpine sh
docker build -t foo .       →   podman build -t foo .
docker compose up           →   podman compose up        # via podman-compose
docker ps                   →   podman ps
docker exec -it <id> sh     →   podman exec -it <id> sh
docker logs -f <id>         →   podman logs -f <id>
docker network ls           →   podman network ls
docker volume create x      →   podman volume create x

If your fingers keep typing docker, install podman-docker. It symlinks /usr/bin/docker to /usr/bin/podman so old scripts keep working. Bazzite ships it by default.

# Where the abstraction leaks

  • docker compose v2 vs podman-compose. Parity is close but not perfect. The compose plugin shipped with Podman 5.x handles most multi-container apps. If you hit a wall, Quadlet units are closer to how production actually runs anyway.
  • Docker socket. Tools that hardcode /var/run/docker.sock need a podman socket. Run systemctl --user enable --now podman.socket, then point them at DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock.
  • NVIDIA passthrough. The container toolkit supports both runtimes. The podman path needs --security-opt=label=disable on SELinux distros.
  • Buildkit features. docker build --secret, --ssh, --mount=type=cache all have podman equivalents. Flag names differ slightly. Check podman build --help.
  • macOS / Windows. Podman runs a Linux VM under the hood (podman machine). Resource limits and bind mounts work, but host.docker.internal becomes host.containers.internal.

# Why it matters on immutable distros

On Bazzite and other rpm-ostree systems, you avoid layering RPMs into the base image (see rpm-ostree: rebase, pin, rollback). Containers are how you install traditional CLI tooling. distrobox and toolbx both wrap Podman to give you a transparent shell into a container with your home directory mounted in. That's where gcc, kubectl, terraform, etc. should live.

# References