Run rootless containers with Podman instead of Docker

Every time you run docker run, a daemon owned by root is listening on a Unix socket, ready to execute whatever you throw at it. On a shared or single-purpose VPS, that is unnecessary risk. Podman was built to fix exactly that: no daemon, no root socket, and containers that run under your user ID from the first command. In this guide you will install Podman on a fresh Ubuntu 24.04 (the steps are identical for Debian 12), run a rootless container, map ports, set up a systemd user service, and migrate a Docker Compose stack, all without ever typing sudo.
Prerequisites
- A Linux VPS running Ubuntu 24.04 LTS or Debian 12. A Linux VPS with full root access is what you need.
- A non-root sudo user (you will only use sudo to install packages, nothing else).
- Basic familiarity with the terminal and container concepts.
- Subordinate ID ranges allocated (Podman needs them for rootless mapping, we will show you how to verify).
Why run containers without root at all
The Docker model gives a single daemon, dockerd, unlimited root power over the host. A vulnerability in the daemon, or in any container the daemon starts, becomes a host compromise. Podman flips this: each user runs containers with their own UID inside the host namespace. The Podman binary communicates directly with the kernel via libpod; there is no central process to attack. On a VPS that hosts multiple applications or multiple users, rootless containers are the sensible default.
Podman is a drop-in replacement at the CLI level: most Docker commands become podman with the same flags. It supports Docker Compose files (via podman-compose or podman play kube) and integrates with systemd so your containers survive reboots without a daemon.
无守护进程模式消除了 root 套接字攻击面,非常适合 VPS 环境。
Running containers without a daemon eliminates the root-socket attack surface, ideal for a VPS environment.
Step 1, Install Podman on Ubuntu 24.04 or Debian 12
Podman is in the default repos for both distributions. Install it without pulling in Docker compatibility packages:
sudo apt update
sudo apt install podman fuse-overlayfs slirp4netns -y
podman version
Output should show something like Client: 4.9.3, the exact minor version depends on your distro updates. The fuse-overlayfs and slirp4netns are required for rootless storage and user-mode networking.
Verify that a normal user can run Podman without sudo:
podman info --format '{{.Host.Security.Rootless}}'
Expected output: true. If it returns false, you are still running as root, exit and re-ssh as your normal user.
Step 2, Check and configure subordinate ID ranges
Rootless Podman maps container UIDs to a range of unprivileged host UIDs. These ranges live in /etc/subuid and /etc/subgid:
grep yourusername /etc/subuid /etc/subgid
If nothing shows, add the ranges (substitute your actual username):
sudo usermod --add-subuids 100000-165535 yourusername
sudo usermod --add-subgids 100000-165535 yourusername
The ranges take effect immediately, no logout needed. Verify again with the grep command above.
Step 3, Pull and run your first rootless container
No sudo, no daemon. Just run:
podman pull docker.io/library/nginx:alpine
podman run -d --name web -p 8080:80 nginx:alpine
curl -s -o /dev/null -w '%{http_code}' http://localhost:8080
Expected output: 200. You now have Nginx running under your user ID, on a non-privileged port (8080). The container can only bind to ports above 1024, that is a security feature, not a limitation. If your application needs port 80 or 443, see the section on authbind or systemd socket activation below.
Step 4, Run containers on privileged ports (80/443)
Non-root users cannot bind to ports below 1024. The standard solution is a reverse proxy that listens on 80/443 as root and forwards to the container. Install Nginx on the host and proxy_pass to localhost:8080:
sudo apt install nginx -y
Create /etc/nginx/sites-available/podman-app:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
}
sudo ln -s /etc/nginx/sites-available/podman-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Now http://example.com reaches the rootless Nginx container without ever giving the container root access to port 80. A WordPress VPS with LiteSpeed can use the same pattern: LiteSpeed on the host reverse-proxies to a rootless PHP container.
Step 5, Run Podman containers as systemd user services
The killer feature of Podman for production is that it integrates directly with systemd --user. A container started as a user service restarts on reboot, survives logouts, and appears in journalctl --user.
Generate a systemd unit from a running container:
podman generate systemd --new --name web > ~/.config/systemd/user/web.service
mkdir -p ~/.config/systemd/user
podman generate systemd --new --name web > ~/.config/systemd/user/web.service
systemctl --user daemon-reload
systemctl --user enable --now web.service
systemctl --user status web.service
Expected: the service is active and the container is running. Log out and back in, then run curl localhost:8080, the container is still there.
Important: Enable lingering for your user so services start at boot:
sudo loginctl enable-linger yourusername
Step 6, Migrate a Docker Compose stack to Podman
Podman can run Docker Compose files directly via podman-compose. Install it:
pip install podman-compose --user
Ensure ~/.local/bin is in your $PATH, then run:
podman-compose -f docker-compose.yml up -d
podman-compose ps
Most Compose files work without changes. The main difference is that Podman does not use Docker networks, it uses CNI (Container Network Interface) or Netavark, which is transparent to the application. If you rely on Docker-specific features like docker-compose logs --tail, replace the command with podman logs -f on individual containers.
For full compatibility with complex Compose files, you can also convert them to Kubernetes YAML and run with podman play kube:
podman play kube --down stack.yaml
podman play kube stack.yaml
This is becoming the preferred deployment mode for production rootless stacks.
Troubleshooting
Container cannot write to mounted volumes
Rootless containers run as a high UID (e.g., 100000) inside the user namespace. Host directories must be writable by that UID or you need to set podman unshare to adjust permissions:
podman unshare chown 100000:100000 /path/to/host/dir
Network: no external connectivity from inside the container
Rootless networking uses slirp4netns by default, which does not support ICMP (ping). TCP and UDP work normally. If you need ping, switch to the rootful network mode or run the container with --network host (this bypasses the isolation, use only when needed).
Podman CLI hangs when pulling images
Check if registries are configured in ~/.config/containers/registries.conf. On a fresh install, the default points to docker.io, quay.io, and registry.fedoraproject.org. If you are behind a firewall, add your registry mirror:
unqualified-search-registries = ["docker.io"]
[[registry]]
location = "docker.io"
mirror = ["mirror.gcr.io"]
FAQ
Is Podman a complete drop-in replacement for Docker?
For most workflows, yes. The CLI is identical for the common commands (run, ps, exec, logs, build). The main differences are: no daemon, no Docker Swarm, and Compose support requires podman-compose or manual conversion. Kubernetes-native deployments via podman play kube are cleaner than Docker Compose for rootless production stacks.
Can I run Podman on a Windows Server VPS?
No. Podman runs on Linux natively. On a Windows Server VPS with RDP access, you would need WSL2 or a Linux VM inside the Windows host to run Podman, which adds overhead. For Windows-native containers, Docker Desktop remains the standard.
Does Podman support Docker Compose natively?
Not in the Podman binary itself. You need podman-compose (a Python wrapper) or convert the Compose file to Kubernetes YAML and use podman play kube. The latter is the recommended path for production because it avoids any Docker dependency.
Is rootless Podman slower than Docker?
In practice, the difference is negligible for most applications. The overhead comes from user-mode networking (slirp4netns) and FUSE-based storage. Using --network host eliminates the networking overhead at the cost of isolation. Real-world benchmarks show Podman within 5-8% of Docker for I/O and CPU-bound tasks.
How do I configure Podman to use a proxy or custom registry?
Edit ~/.config/containers/registries.conf for registry mirrors. For HTTP proxy, set HTTP_PROXY and HTTPS_PROXY in ~/.config/systemd/user/container-*.service or in the environment file referenced by the service unit.
What happens if I accidentally run podman run --privileged?
It works, the container gets full capabilities inside its user namespace, but it still cannot access the host's kernel directly. Rootless --privileged is far less dangerous than the Docker equivalent, but still grants more access than necessary. Avoid it unless you are debugging a kernel interaction.
Related articles
- Manage Docker containers with Portainer on a VPS
- Docker networking explained: bridge, host, overlay
- Run multiple sites on one VPS with Docker and Nginx proxy
- Run system containers with LXC and LXD on a VPS
在 VPS 上使用 Podman 运行无根容器
Podman 是一款无守护进程的容器运行时,允许普通用户在没有 root 权限的情况下运行容器。它在 Ubuntu 24.04 或 Debian 12 上安装简单,支持 Docker Compose 和 systemd 用户服务管理。对于 VPS 环境,无根模式减少了攻击面,同时保持与 Docker 命令的高度兼容性。建议在越南 Linux VPS 上部署生产应用时优先使用 Podman 替代 Docker。


