Anyone in the docker group can effectively become root because the Docker daemon runs as root and obeys commands sent through its Unix socket. Podman takes a different approach by eliminating the central daemon and supporting rootless containers, pods, and Quadlet services managed through systemd.
Imagine a developer needs to run containers on a shared build server, so you add their account to the docker group. That seemingly harmless change can give them root-level access to the host.
For example, they could run docker run -v /:/host and access or modify host files because the Docker daemon (dockerd) runs with root privileges and executes requests received through its socket.
Docker’s own documentation explicitly warns that membership in the docker group grants root-level privileges, making it a significant concern in security-conscious environments. Docker Engine does provide a rootless mode, but it is not the default and is not always enabled on production servers.
Podman removes the daemon entirely, runs containers as child processes of the user who starts them, and uses Linux user namespaces to map container root to an unprivileged host user.
It also supports Kubernetes-style pods and integrates with systemd through Quadlet, making it possible to manage containers as native services.
In this guide, we’ll look at how Podman’s rootless architecture works, how it compares with Docker from a security perspective, and how to run containers, pods, and systemd-managed services without giving users unnecessary root-level access.
How Docker Engine and Podman Start a Container
Both Docker Engine and Podman run the same OCI-compliant container images. The important difference is what sits between your command and the container, how those components are managed, and which user owns them.
With Docker Engine, the docker command acts as a client. It sends API requests through /var/run/docker.sock to the root-owned dockerd daemon. Docker then uses containerd and an OCI runtime such as runc to create and manage containers.
By default, this entire control path is tied to the root-owned Docker daemon, which is why access to its socket effectively provides root-level control over the host.
If dockerd stops, Docker normally loses control of its containers as well. Docker’s live-restore feature can keep containers running while the daemon is unavailable, but it does not remove the daemon from the architecture.
Podman takes a different approach. There is no central daemon waiting for client requests. The podman command directly creates and starts the container, using conmon (container monitor) to keep watch over the container process and an OCI runtime, usually crun, to create the container.
Because each container has its own monitor process, Podman does not require a shared daemon to remain running after the container has started. In rootless mode, these processes can run under the regular user’s account rather than through a root-owned service.
This daemonless design also fits naturally with systemd and Quadlet, allowing containers and pods to be managed as user or system services without introducing a permanently running container daemon.
If this finally explained why the docker group shows up in every security audit, share it with someone who still adds developers to it by default.
Install Podman on Rocky Linux and Ubuntu
Now that you know what changes under the hood, let’s install Podman on the lab server (192.168.122.248) as the regular tecmint user with sudo access. There is no daemon to enable afterwards.
On Rocky Linux, AlmaLinux, and RHEL, Podman is available through the AppStream repository, so dnf can install it directly:
sudo dnf install -y podman
On Ubuntu and Debian, refresh the package index first so apt installs the latest packaged version:
sudo apt update sudo apt install -y podman
The exact Podman version depends on the distribution and release. For example, Ubuntu 26.04 LTS currently packages Podman 5.7.0, while the upstream Podman documentation is already in the 6.1 series.
The commands and rootless workflow used in this guide are compatible with Podman 5 and 6.
Configure Subordinate User IDs
Rootless Podman uses Linux user namespaces to map container users to unprivileged host users. For this mapping to work normally, the user needs subordinate UID and GID ranges in /etc/subuid and /etc/subgid. Podman checks these files when creating rootless containers.
Check whether the tecmint account already has the required ranges:
grep tecmint /etc/subuid /etc/subgid
You should see output similar to:
/etc/subuid:tecmint:100000:65536 /etc/subgid:tecmint:100000:65536
If either file has no entry for tecmint, add a subordinate UID and GID range:
sudo usermod --add-subuids 100000-165535 \
--add-subgids 100000-165535 tecmint
Then migrate the user’s existing Podman state:
podman system migrate
The migration step is important when changing subordinate ID ranges because rootless Podman can keep a pause process and existing user namespace alive with the previous mappings. podman system migrate stops the relevant containers and pause process so the new /etc/subuid and /etc/subgid mappings can take effect.
At this point, tecmint has everything needed to start using Podman in rootless mode, without belonging to the privileged docker group.
Compare Podman and Docker with Real Workloads
With Podman installed, the clearest way to see the difference is to run workloads you would normally run with Docker and then inspect which host user owns the resulting processes.
Example 1: Check Which Host User Owns a Container Process
Example 1: Check Which Host User Owns a Container Process
Start an Alpine container as tecmint without sudo, then look at its process from the host side:
podman run -d --name sleeper docker.io/library/alpine sleep 600 ps -o user,pid,cmd -C sleep
You should see output similar to::
USER PID CMD tecmint 4821 sleep 600
Now check the user from inside the container:
podman exec sleeper whoami
The command prints:
root
This is the important distinction: root inside a rootless container is not root on the host. Podman uses a user namespace to map the container’s root user to an unprivileged host identity. From the host’s perspective, the sleep process belongs to tecmint.
For comparison, start the same workload through Docker’s root-owned daemon:
sudo docker run -d --name sleeper-docker docker.io/library/alpine sleep 600 ps -o user,pid,cmd -C sleep
The Docker container’s process appears as owned by root on the host.
Rootless Podman therefore reduces the impact of a container compromise or escape: an attacker breaking out of the container does not automatically gain host-root privileges. They initially inherit the privileges of the host user running the container. That still matters, so the tecmint account should have only the permissions it actually needs.
Clean up both test containers before continuing:
podman rm -f sleeper sudo docker rm -f sleeper-docker
If you have ever had to justify container access to a security team, share this with a colleague who still runs every container through sudo docker.
Example 2: Group a Web Server and Database in a Pod
The first example ran a single container, but real applications often consist of multiple containers that need to communicate. Docker typically handles this with a user-defined network, while Podman can place related containers in a pod that shares a network namespace.
Create a pod and publish port 8080 on the host to port 80 inside the pod:
podman pod create --name webstack -p 8080:80
Now start MariaDB and Nginx inside the same pod:
podman run -d --pod webstack --name db -e MARIADB_ROOT_PASSWORD=ChangeMe123 docker.io/library/mariadb:11 podman run -d --pod webstack --name web docker.io/library/nginx:alpine
Check the pod:
podman pod ps
You should see output similar to:
POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS 3f9c2a71b0de webstack Running 40 seconds ago 8a1d5e2c4f90 3
Seeing three containers even though you started only two is normal. Podman creates an additional infra container for the pod. It holds the pod’s shared network namespace open so that the other containers can share it.
Because the containers in a pod share the same network namespace, they can reach each other through localhost. For example, the Nginx container could connect to MariaDB at:
127.0.0.1:3306
This example does not configure Nginx to use MariaDB, so the connection is only demonstrating the networking model. In a real application, the web container would need application-level configuration and credentials to communicate with the database.
Podman can also generate Kubernetes-compatible YAML from the pod:
podman kube generate webstack > webstack.yaml
You can then use that YAML to recreate the workload:
podman kube play webstack.yaml
This gives Podman a useful bridge between local containers and Kubernetes-style workload definitions. Docker provides other mechanisms for defining multi-container applications, such as Compose, but it does not use the same Podman kube generate and kube play workflow for exporting and recreating pods.
Example 3: Run a Container as a systemd Service with Quadlet
The pod from Example 2 won’t return after a reboot, because no daemon restarts it. Podman hands that job to systemd through Quadlet, which turns a .container file in your home directory into a systemd unit:
mkdir -p ~/.config/containers/systemd
On Ubuntu and Debian, create the unit file with nano:
nano ~/.config/containers/systemd/webapp.container
On Rocky Linux, AlmaLinux, and RHEL, minimal installs ship with vi rather than nano, so use vi there:
vi ~/.config/containers/systemd/webapp.container
Then add the following configuration to the file:
# ~/.config/containers/systemd/webapp.container [Unit] Description=Nginx web container managed by Quadlet [Container] Image=docker.io/library/nginx:alpine ContainerName=webapp PublishPort=8081:80 AutoUpdate=registry [Service] Restart=always [Install] WantedBy=default.target
Here:
Imageuses the full registry path, whichAutoUpdaterequires.ContainerNamereplaces the default namesystemd-webapp.PublishPortuses port 8081 to avoid clashing with the Example 2 pod.AutoUpdate=registrylets podmanauto-updatepull newer images.Restart=alwaysrestarts the container whenever it stops.WantedBy=default.targetstarts it with your user session, replacingsystemctl enable.
Save the file and exit the editor. Since a rootless user’s systemd instance stops at logout, enable lingering first, then load and start the generated unit:
sudo loginctl enable-linger tecmint systemctl --user daemon-reload systemctl --user start webapp.service
If start fails with Unit webapp.service not found, Quadlet rejected the file, and /usr/libexec/podman/quadlet -dryrun -user prints the exact error. Once it starts, test a failure by killing the container and checking the service a few seconds later:
podman kill webapp systemctl --user status webapp.service --no-pager
Output:
● webapp.service - Nginx web container managed by Quadlet
Loaded: loaded (/home/tecmint/.config/containers/systemd/webapp.container; generated)
Active: active (running) since Tue 2026-10-06 11:42:17 IST; 4s ago
The 4s ago timestamp shows systemd started a fresh container without root.
If Quadlet just replaced a pile of docker run --restart scripts for you, share it with the person who maintains your server runbooks.
When to Choose Podman or Docker Engine
After those three examples, the choice usually depends on your environment. Docker Engine remains easier when your team relies on Docker Desktop, Docker Swarm, or BuildKit-specific features, since those tools are built around the Docker daemon.
Podman fits better on shared servers, RHEL-based fleets, and anywhere an audit asks who can become root. The podman-docker package provides a docker command that calls Podman, and tools expecting a Docker socket, such as CI runners, can use Podman’s API socket after systemctl --user enable --now podman.socket.
One limitation catches most people. Rootless containers can’t bind ports below 1024, so -p 80:80 fails with rootlessport cannot expose privileged port 80. Either publish a high port behind a reverse proxy, or lower the limit with sudo sysctl net.ipv4.ip_unprivileged_port_start=80 and save it in /etc/sysctl.d/.
Conclusion
Podman runs the same images as Docker Engine without a root daemon, maps container root to a normal user, and lets systemd manage containers through Quadlet. On shared servers, that removes the biggest risk of a default Docker setup without changing your daily commands.
If you’re working toward Red Hat certification, our RHCSA (RHEL 10 / EX200) course on Pro tecmint covers the RHEL skills rootless containers depend on, including systemd and SELinux.
We’d love to hear how your team runs containers today. Have you moved production workloads to rootless Podman, or is Swarm keeping you on Docker? Share your Quadlet files or migration errors in the comments.

