How to Use Podman as a Docker Replacement in Real Scripts
Use Podman as a Docker replacement in scripts: alias, rootless defaults, Compose, systemd units, port and volume differences, and what does not carry over.

Podman was designed as a daemonless, rootless engine with a Docker-compatible command line, and that makes it easy to try on an existing project. Most scripts that call docker run or docker build work unchanged with Podman. The differences are in the engine: where images are stored, who owns files and how services start. The examples use Podman 5.2 on Fedora and Ubuntu 24.04; the original is in Docker run flags.
The drop-in
sudo apt install podman podman-docker # Debian, Ubuntu
alias docker=podman # or rely on the podman-docker shim
docker run --rm -it docker.io/library/alpine:3.20 echo hello
docker build -t myapp:1.0 .
docker ps --format '{{.Names}} {{.Status}}'The podman-docker package installs a docker command that calls Podman, and a compatibility socket. Note the fully qualified image name: Podman does not assume Docker Hub, and a short name such as alpine triggers a prompt or a configured search registry list, which breaks unattended scripts. Use full names such as docker.io/library/alpine, or set unqualified-search-registries in /etc/containers/registries.conf.
Rootless by default
podman run -d --name web -p 8080:80 docker.io/library/nginx:1.27
podman run -d -p 80:80 docker.io/library/nginx:1.27
sudo sysctl net.ipv4.ip_unprivileged_port_start=80
podman unshare ls -ln ~/dataError: rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf
Without root, a container runs in a user namespace, so the user inside maps to a range of subordinate IDs on the host. Files in bind mounts can appear owned by a strange UID outside, and podman unshare shows the view from inside the namespace. Ports below 1024 need the sysctl above or a reverse proxy. Volumes may need the :Z suffix on SELinux systems, which relabels the files for the container.
Services without a daemon
# ~/.config/containers/systemd/web.container
[Unit]
Description=Web
[Container]
Image=docker.io/library/nginx:1.27
PublishPort=8080:80
Volume=%h/site:/usr/share/nginx/html:ro,Z
[Service]
Restart=always
[Install]
WantedBy=default.targetPodman has no daemon to restart containers after a reboot, so services are managed by systemd. Quadlet turns a .container file into a unit when you run systemctl --user daemon-reload, and systemctl --user enable --now web.service starts it. The older podman generate systemd is deprecated in favour of this. Lingering lets user services run without a login, as in loginctl enable-linger.
Compose and the socket
systemctl --user enable --now podman.socket
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
docker compose up -dDocker Compose can talk to Podman through its API-compatible socket, and podman compose calls an installed Compose implementation. Most files work; features that rely on Docker-specific networking, network_mode: host semantics or BuildKit-only options can differ.
Images, storage and the first run
Podman keeps images and containers in the user's own directory when it runs rootless, normally ~/.local/share/containers/storage, and in /var/lib/containers when run as root. The two stores are separate, so an image pulled by one is invisible to the other. That separation is a feature, since a compromised container cannot see the root store, and a cost, because the same image is downloaded twice if both are used. The storage driver is usually overlay on a modern kernel. Rootless setups use native overlay when the kernel supports it, and otherwise fall back to fuse-overlayfs, which is slower.
The registry list is configured in /etc/containers/registries.conf. Setting unqualified-search-registries = ["docker.io"] restores the Docker behaviour for short names, and using fully qualified names removes the ambiguity, which also removes a supply chain risk: with several search registries, a short name can resolve to an image from whichever registry answers first.
Pods, networking and DNS
Podman adds a concept Docker lacks, the pod: a group of containers that share a network namespace and can reach each other on localhost, just like containers in a Kubernetes pod. podman pod create --name app -p 8080:80 followed by podman run --pod app places containers in it. podman generate kube exports a pod as Kubernetes YAML, and podman play kube runs such a file, so a pod can be developed locally and moved to a cluster. For networking, rootless containers use pasta or slirp4netns to reach the network without privilege, and user-defined networks give name resolution between containers, as in Docker's custom bridges. Throughput through the rootless path is lower than a native bridge, which is visible only under heavy traffic.
Build behaviour
The engine builds with Buildah. A Dockerfile works, though Podman also reads a Containerfile with the same syntax. BuildKit-specific syntax such as cache mounts in a RUN --mount=type=cache line is supported in current releases, but some advanced frontends and secret mount options differ, so a CI file that depends on BuildKit features should be tried before switching. The image format defaults to OCI, and a --format docker flag is needed for features like HEALTHCHECK to be stored in the image.
Security trade-offs in practice
Rootless operation limits the damage of a container escape to the unprivileged user, and the lack of a long-running root daemon removes a target. The trade is operational: privileged ports, certain volume ownership cases, and some tools that expect to mount filesystems need extra setup. SELinux on Red Hat family systems adds labelling, so a bind mount may need the :Z or :z suffix to be readable inside the container. Forgetting it produces "permission denied" errors that look like ownership problems but are label problems, and ausearch -m avc confirms it.
Migrating a team works best in stages: first developers use it locally with the alias, then CI builds with it, then a service is moved to Quadlet on a non-critical host. Keep the Docker path working until each stage has a week of use behind it.
Volumes, ownership and the user namespace
Bind mounts are where rootless containers surprise people. The user inside the container, often root with id 0, maps to the invoking user on the host, and other ids map to the subordinate range listed in /etc/subuid and /etc/subgid. A directory owned by the host user therefore looks like it is owned by root inside the container, which is convenient, but a file created inside by a non-root container user lands on the host with a high numeric owner that the host user cannot edit without podman unshare. The option --userns=keep-id maps the host user to the same id inside, which solves most development cases where an application writes into a mounted source tree.
Named volumes avoid the problem for data that only the container touches. They live in the storage directory, are created with the right ownership, and survive container removal. Back them up with podman volume export and restore with import, and test the restore, since a backup nobody has restored is only a hope. For databases, check the vendor's guidance on running as a non-root user inside a rootless container, because some images assume they start as root and change user themselves.
Resource limits and systemd
Memory and CPU limits work through cgroups, and rootless Podman needs cgroup v2 with delegation enabled for the user. On a current systemd distribution that is already true for the controllers that matter most, but cpu and io may need a drop-in file to be delegated. Verify by running a container with --memory 256m and inspecting the cgroup, and if the limit is ignored, check /sys/fs/cgroup/user.slice/user-$UID.slice/cgroup.controllers. Without delegation the flag is accepted with a warning and nothing is enforced, which is easy to miss.
Where this does not apply
Scripts that parse the output of docker info or depend on the daemon, tools that need /var/run/docker.sock, and Swarm do not carry over. Performance and storage drivers differ, and macOS and Windows both use a VM through podman machine. Measure and test the whole pipeline before switching, and compare the options in Docker versus Podman versus containerd and rootless containers. See Podman Quadlet documentation and the Podman documentation.


