Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Podman: A Daemonless Docker Alternative

By Kokil Thapa | Last reviewed: September 2026

Podman: A Daemonless Docker Alternative solves a problem Docker created on every Linux server I maintain: one long-running daemon with root privileges, a Unix socket, and a single point of failure. If that daemon dies or gets compromised, every container on the host is at risk. Podman runs each container as an ordinary process forked from your shell or systemd unit. There is no central broker. For teams running Linux system administration on Ubuntu EC2 boxes alongside Laravel apps, that architecture change matters more than a CLI rename.

What makes Podman a daemonless Docker alternative?

Docker's model depends on dockerd, a persistent root-owned daemon. Your CLI talks to it over /var/run/docker.sock. Every docker run is really a remote procedure call. That design is convenient, but it concentrates privilege.

Podman inverts the model. When you run podman run nginx, Podman forks, execs conmon (the container monitor), and starts the OCI runtime (crun or runc). The container becomes a child process tree under your user or systemd—not under a shared daemon. Kill Podman and you kill one container, not the entire runtime.

Docker vs Podman ArchitectureDocker (daemon)docker CLIdockerdroot daemonAll containers via one socketPodman (daemonless)podman CLIfork/execper containerconmon + crun/runc per unitShared OCI EcosystemSame images from Docker Hub, GitLab, ECRBuildah builds · skopeo copies · systemd manages
Podman as a daemonless Docker alternative: no central dockerd, each container is an independent process tree

Both tools consume the same OCI image format. A image you build with Docker or multi-stage Docker builds runs unchanged in Podman. The difference is operational security and process ownership, not container contents.

Core components you should know

  • Podman — runs and manages containers and pods.
  • Buildah — builds OCI images; podman build wraps it.
  • skopeo — copies and inspects images across registries without a daemon.
  • conmon — monitors container I/O and exit codes.
  • Netavark — modern networking stack replacing legacy CNI plugins in recent Podman releases.

On Fedora and RHEL-family systems, Podman is the default container engine. On Ubuntu servers where I deploy Laravel with GitLab CI, Podman is an opt-in replacement that fits existing Docker-on-Ubuntu knowledge without retraining the whole team.

CriteriaDocker EnginePodman
ArchitectureCentral dockerd daemonDaemonless fork/exec per container
Rootless defaultSupported but secondaryFirst-class; recommended path
systemd integrationRequires manual unit filesNative; Quadlet and podman generate systemd
Kubernetes podsNot nativeNative podman pod concept
Socket attack surfacedocker.sock grants root-equivalent accessNo daemon socket in default mode
Compose supportBuilt-in docker composepodman-compose or Docker Compose with Podman socket
macOS/Windows devDocker Desktop VMpodman machine lightweight VM

How do you install Podman on Ubuntu for production use?

Ubuntu 24.04 ships Podman in the default repositories. Ubuntu 22.04 may need the Kubic stable repo for a current version. I prefer the distro package on 24.04 and the Kubic repo on older LTS boxes still running PHP-FPM workloads.

Install on Ubuntu 24.04

sudo apt update
sudo apt install -y podman podman-compose

podman --version
podman info --format '{{.Host.Security.Rootless}}'

Install on Ubuntu 22.04 via Kubic repo

sudo apt install -y curl gnupg2
source /etc/os-release
echo "deb https://download.opensuse.org/reports/deb/xUbuntu_${VERSION_ID}/ /" \
  | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
curl -fsSL https://download.opensuse.org/reports/deb/xUbuntu_${VERSION_ID}/Release.key \
  | sudo gpg --dearmor -o /etc/apt/keyrings/libcontainers-archive-keyring.gpg
sudo apt update
sudo apt install -y podman

Verify rootless prerequisites before you run anything in production. Podman needs user namespaces enabled. On most Ubuntu kernels they are on by default. Check with:

cat /proc/sys/kernel/unprivileged_userns_clone
sysctl kernel.unprivileged_userns_clone

A value of 1 means rootless Podman will work. If it reads 0, rootless mode fails with obscure permission errors. Fix it in /etc/sysctl.d/99-rootless.conf and reload.

Enable Docker CLI compatibility

The podman-docker package installs a docker shim that forwards to Podman. Useful for legacy scripts and CI jobs that call docker blindly.

sudo apt install -y podman-docker
docker --version
docker ps

Do not assume every Docker flag maps 1:1. Swarm commands, certain buildx features, and legacy links syntax differ. Test your critical paths on a staging host first. Our Podman vs Docker migration guide covers the common breakages.

How does rootless Podman work and what breaks?

Rootless mode is the main reason teams adopt Podman on shared servers. A compromised app container cannot easily pivot to full root through a daemon socket—because there is no daemon socket.

When user deploy runs a container rootlessly, Podman maps UIDs inside the container to a range defined in /etc/subuid and /etc/subgid. Root inside the container (UID 0) becomes UID 100999 (or similar) on the host. Files written to bind mounts carry that mapped ownership.

Rootless UID MappingHost (Ubuntu)User: deploy (UID 1000)subuid: 100000:65536No dockerd socketContainer NSnginx runs as UID 101root in NS = UID 0Mapped to host 100101Bind Mount./storage/logsOwner: 100101PHP-FPM may not readCommon Gotcha: Volume PermissionsLaravel storage/ owned by www-data in containerFix: chown on host, or --userns=keep-id, or named volumesSee docker networking and volumes for mount patterns
Rootless Podman UID mapping: why bind-mount permissions break Laravel and PHP apps without planning

Configure subuid and subgid for deploy users

sudo usermod --add-subuids 100000-165535 deploy
sudo usermod --add-subgids 100000-165535 deploy
grep deploy /etc/subuid /etc/subgid

loginctl enable-linger deploy

The enable-linger step matters. Without it, user systemd units stop when the SSH session closes. Production containers managed by systemd user services will die on logout.

Fix the Laravel storage permission trap

On a production Laravel application, I bind-mount storage/ and bootstrap/cache/. Rootless Podman writes those as UID 100999. Host PHP-FPM running as www-data cannot write logs. Three fixes work in practice:

  1. Run the container with --userns=keep-id so container UID matches host UID.
  2. Pre-create directories with chown 100999:100999 storage -R on the host.
  3. Use a named volume instead of a bind mount for writable paths.

I hit this on sister sites sharing a Deployer 7 pipeline. The app worked in rootful Docker locally but failed silently in rootless Podman after deploy. Always test write paths, not just HTTP 200 responses.

Ports below 1024 in rootless mode

Rootless containers cannot bind host ports 1–1024 without extra setup. Use sysctl net.ipv4.ip_unprivileged_port_start=80 or put Nginx on the host as reverse proxy to a high port. On legal-tech portals I often run Apache on the host and keep app containers on 8080+ anyway—same pattern as Court Marriage In Nepal infrastructure.

How do you migrate existing Docker workflows to Podman?

Migration is rarely a big-bang cutover. I run a phased approach on EC2 hosts that already serve PHP-FPM and MySQL outside containers.

Migration Workflow1. Inventoryimages, mounts2. Installpodman + compose3. Stagingparity tests4. systemdQuadlet units5. Cutoverstop dockerdValidate: health checks, logs, restarts, resource limitsUse podman auto-update for registry-pulled imagesKeep dockerd runningParallel until parity provenRollback = start dockerd againDisable dockerdOnly after 48h clean metricssystemctl disable docker
Phased Docker-to-Podman migration: inventory, staging parity, systemd units, then dockerd removal

Step 1: Export your running state

docker ps -a --format '{{.Names}}' > /tmp/docker-containers.txt
docker images --format '{{.Repository}}:{{.Tag}}' > /tmp/docker-images.txt
docker volume ls -q > /tmp/docker-volumes.txt

Step 2: Pull the same images with Podman

podman pull redis:8-alpine
podman pull postgres:18-alpine
podman pull nginx:alpine

Images from Docker Hub, GitLab Container Registry, or a self-hosted Docker registry pull identically. Authentication uses the same ~/.docker/config.json or podman login.

Step 3: Convert run commands

Most one-liners translate directly:

docker run -d --name redis -p 6379:6379 redis:8-alpine

podman run -d --name redis -p 6379:6379 redis:8-alpine

For PostgreSQL dev databases, the same pattern from our PostgreSQL in Docker guide works with podman run. Add --restart=always or prefer systemd for production restarts.

Step 4: Generate systemd units

podman generate systemd --name redis --files --new
mkdir -p ~/.config/systemd/user/
mv container-redis.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now container-redis.service

On modern Podman, Quadlet is cleaner. Drop a .container file in ~/.config/containers/systemd/:

[Container]
Image=docker.io/library/redis:8-alpine
PublishPort=6379:6379
AutoUpdate=registry

[Service]
Restart=always

Run systemctl --user daemon-reload. systemd generates and manages the unit. This beats hand-written ExecStart lines that go stale after image tag changes.

Edge cases that still bite teams

  • Docker Swarm and Compose profiles — not supported natively. Move orchestration to systemd, Nomad, or Kubernetes vs Docker Swarm decision paths.
  • GPU passthrough — requires rootful Podman or specific device cgroup rules.
  • Host networking — rootless host network behaves differently; test before assuming --network host parity.
  • inotify on macOS — use podman machine with virtiofs; bind mounts can be slow.
  • Health checks in old Compose files — verify HEALTHCHECK in Dockerfile or Compose healthcheck: blocks; Podman respects OCI health checks.

Can you run Docker Compose and Laravel Sail with Podman?

Yes, with caveats. Three paths exist in 2026.

Option A: podman-compose (Python)

pip install podman-compose
podman-compose -f docker-compose.yml up -d

Works for simple multi-container dev stacks. Some Compose v3 features lag behind Docker Compose v2. Test volume paths and network aliases.

Option B: Docker Compose with Podman socket

systemctl --user enable --now podman.socket
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
docker compose up -d

This preserves the exact Compose CLI your team knows. The socket exposes a Docker-compatible API backed by Podman. Official Podman docs describe this path for teams migrating CI pipelines.

Option C: Laravel Sail adjustments

Laravel Sail assumes Docker Desktop or dockerd. For local Laravel dev, I still recommend Sail on Docker for the smoothest path—see local Laravel dev with Sail and the Sail vs Docker Compose comparison.

If you must use Podman, set DOCKER_HOST to the Podman socket and install podman-docker. Watch for volume speed on bind-mounted vendor/ directories. On Linux bare metal, performance is usually fine. On podman machine on macOS, run composer install inside the container, not on the mounted host path.

For production Laravel on EC2, I rarely containerize the PHP app itself. I containerize Redis, Meilisearch, or queue workers. Podman fits that sidecar model well alongside Apache and PHP-FPM on the host—a pattern we use on Adventure Third Pole Trek infrastructure.

How do you harden and operate Podman in production?

Daemonless does not mean zero maintenance. You still manage images, logs, updates, and resource limits.

Resource limits

Same flags as Docker—see our guide on limiting Docker container resources:

podman run -d \
  --name queue-worker \
  --memory=512m \
  --cpus=1.0 \
  --pids-limit=100 \
  my-registry/queue-worker:latest

Logging and debugging

podman logs -f queue-worker
podman inspect queue-worker --format '{{.State.Status}}'
podman stats

Parse JSON logs with our JSON formatter tool when apps emit structured logging. Ship logs to journald by default when systemd manages the unit.

Automatic image updates

podman auto-update --dry-run
podman auto-update

Label containers or Quadlet units with AutoUpdate=registry. Podman pulls new digests on a timer. Pair with health checks before you trust auto-updates on payment webhook workers.

Networking with Netavark and pasta

Recent Podman releases use Netavark as the default network backend. Rootless containers get IP via slirp4netns or pasta. For service discovery between containers, use Compose networks or Podman pods:

podman pod create --name app-stack -p 8080:80
podman run -d --pod app-stack --name web nginx:alpine
podman run -d --pod app-stack --name api myapp:latest

Containers in a pod share a network namespace—similar to Kubernetes. Useful when you front an API with Nginx and want localhost communication without overlay DNS.

For reverse proxy integration, Traefik or host Nginx still terminate TLS. See Traefik as reverse proxy for containers and Docker networking and volumes—concepts transfer directly.

Production Stack PatternHost: Ubuntu 24 · Apache/Nginx · PHP-FPM 8.4 · Let's EncryptPodman PodRedis · Queue workersystemd QuadletAutoUpdate · RestartMySQL 9.7Host or containerDeployer 7 + GitLab CI builds assets off-serverContainer images pulled to staging, promoted after smoke testsNo dockerd socket on production host
Typical production Podman layout: PHP on host, sidecar containers under systemd, no central daemon

Security practices that matter

  • Run rootless unless you have a concrete reason not to.
  • Never expose podman.socket to untrusted networks—it recreates Docker's socket risk.
  • Use read-only root filesystem where possible: --read-only with tmpfs mounts for /tmp.
  • Drop capabilities: --cap-drop=ALL and add only what you need.
  • Scan images with podman scan or your registry's scanner before deploy.

The official Podman documentation at docs.podman.io and the upstream project at github.com/containers/podman are the authoritative references for flag changes between releases. Red Hat's rootless guide covers edge cases for enterprise SELinux hosts; Ubuntu uses AppArmor instead, but UID mapping rules are identical.

When should you choose Podman over Docker in 2026?

Choose Podman when security isolation and systemd-native operations matter more than Docker Desktop convenience. Strong fits include:

  • Shared Linux servers where multiple developers SSH in and run containers.
  • CI runners that should not grant jobs root-equivalent socket access.
  • Fedora/RHEL/CentOS Stream environments where Podman is preconfigured.
  • Teams planning a Kubernetes migration who want pod semantics locally.
  • Regulated workloads that audit process trees and reject background daemons.

Stick with Docker when your team depends on Docker Desktop on macOS/Windows, Swarm orchestration, or deep buildx multi-platform features you have not validated on Podman. Our Docker Buildx multi-platform guide and Compose multi-container setup remain the fastest path for pure Docker shops.

On budget-sensitive Nepal hosting (Rs 2,500–5,000/month VPS, ~USD 19–37), a single Ubuntu VPS running rootless Podman sidecars plus host PHP often beats paying for managed container platforms. You keep control without Kubernetes overhead.

For ongoing ops after migration, support and maintenance covers the systemd units, backups, and upgrade paths that outlive the initial install. If you are containerizing a new enterprise application, decide Podman vs Docker during architecture review—not after the first production incident.

Key Takeaways

  • Podman runs OCI containers without dockerd; each container is an independent process tree managed by conmon and crun/runc.
  • Rootless mode removes the docker.sock attack surface but requires subuid/subgid setup and careful bind-mount ownership for Laravel and PHP apps.
  • Migration is phased: install Podman alongside Docker, validate on staging, generate systemd or Quadlet units, then disable dockerd.
  • Docker Compose works via podman-compose or DOCKER_HOST pointing at podman.socket; Laravel Sail still prefers native Docker on dev machines.
  • Production hardening means resource limits, auto-update labels, health checks, and keeping the Podman socket off public interfaces.
  • Choose Podman for Linux server security and systemd integration; keep Docker where Desktop, Swarm, or untested buildx workflows dominate.

People Also Ask

Is Podman fully compatible with Docker commands?

Most daily commands—run, ps, build, pull, exec, logs—are compatible, especially with the podman-docker shim. Swarm, some legacy network flags, and certain buildx features differ. Always test your CI scripts on a staging host before cutover.

Does Podman work on macOS and Windows?

Yes, through podman machine, which starts a lightweight Linux VM similar to Docker Desktop's backend. Install via Homebrew on macOS or the Windows installer. Performance on bind mounts can lag native Linux; prefer named volumes or run package managers inside the container.

Can Podman replace Docker in Kubernetes?

Podman is not a Kubernetes replacement. It complements Kubernetes by running local pod-like groups and generating systemd units on single nodes. Kubernetes still orchestrates clusters; Podman is the node-level runtime alternative to containerd paired with dockerd on bare VMs.

Is rootless Podman slower than Docker?

Startup overhead is slightly higher due to user namespace setup, but runtime performance is comparable for typical web workloads. Network throughput on rootless slirp4netns can lag host networking; use pasta or host-published ports for latency-sensitive services. CPU and memory limits behave like Docker's cgroup v2 controls.

Run containers without the daemon tax

Podman: A Daemonless Docker Alternative earns its place on production Linux hosts where a root-owned dockerd socket is the weakest link. You keep OCI images, Compose files, and most CLI muscle memory. You gain rootless defaults, systemd-native lifecycle management, and a smaller attack surface. Start with sidecar services—Redis, workers, search—on a staging box, fix volume permissions before you touch Laravel storage, and cut over when metrics stay clean for 48 hours.

If you want help auditing a Docker-based server or planning a Podman migration on Ubuntu, contact us for a practical review. For related reading, see the full migration checklist, container registry comparison, and about the author.

Frequently Asked Questions

Podman runs OCI containers by fork/exec from your shell or systemd, not through a central dockerd daemon. Each container is an independent process tree.

Yes. Podman is open source with no per-host licensing fee, unlike Docker Desktop commercial terms on some platforms.

Choose Podman when rootless security, systemd-native ops, and no daemon socket matter more than Docker Desktop or Swarm convenience.

Docker depends on dockerd, a persistent root-owned daemon. Your CLI sends remote procedure calls over /var/run/docker.sock. Podman inverts that model: podman run forks, execs conmon as the container monitor, and starts the OCI runtime crun or runc. The container becomes a child process under your user or systemd, not a shared broker. Kill Podman and you stop one container, not the entire runtime. Both tools consume the same OCI image format, so images built with Docker or multi-stage builds run unchanged in Podman.

Ubuntu 24.04 ships Podman in default repositories: apt update, apt install podman podman-compose, then verify with podman --version and podman info. Ubuntu 22.04 typically needs the Kubic stable repo from download.opensuse.org before installing. I prefer the distro package on 24.04 and Kubic on older LTS boxes still running PHP-FPM workloads. Also install podman-docker if legacy scripts call docker blindly. Before production, confirm rootless prerequisites: cat /proc/sys/kernel/unprivileged_userns_clone must read 1. If it reads 0, fix it in /etc/sysctl.d/99-rootless.conf and reload sysctl.

Rootless Podman maps container UID 0 to a host UID from /etc/subuid and /etc/subgid ranges, so a compromised app cannot pivot through a daemon socket that does not exist. Configure deploy users with usermod --add-subuids and --add-subgids, then run loginctl enable-linger so systemd user units survive SSH logout. Bind mounts inherit mapped ownership, which breaks Laravel storage/ and bootstrap/cache/ writes when host PHP-FPM runs as www-data. Ports 1–1024 cannot bind without net.ipv4.ip_unprivileged_port_start=80 or a host reverse proxy on a high port. I have seen apps return HTTP 200 while failing silently on write paths after deploy.

Yes. Podman consumes the same OCI image format Docker uses. An image you build with Docker, including multi-stage Docker builds, runs unchanged in Podman. Pull from Docker Hub, GitLab Container Registry, or a self-hosted registry with podman pull using the same tags, for example redis:8-alpine or postgres:18-alpine. Authentication reuses ~/.docker/config.json or podman login. The operational difference is process ownership and security model, not container contents. Migration inventory starts by listing existing docker images and pulling identical tags with Podman on a staging host before cutover.

The podman-docker package installs a docker shim that forwards CLI calls to Podman, which helps legacy scripts and CI jobs that invoke docker without knowing the backend changed. After install, docker --version and docker ps should work against Podman. Do not assume every Docker flag maps one-to-one. Docker Swarm commands, certain buildx features, and legacy links syntax differ or are unsupported. Test critical paths on staging before production cutover. This shim is useful for incremental migration, not as proof that your entire Docker workflow is compatible without validation.

Use a phased approach, not a big-bang cutover. Step one: inventory running containers, images, and volumes with docker ps, docker images, and docker volume ls. Step two: pull the same tags with podman pull. Step three: translate run commands directly, for example docker run -d --name redis becomes podman run with identical flags. Step four: generate systemd units via podman generate systemd --files --new, or prefer Quadlet .container files under ~/.config/containers/systemd/ with AutoUpdate=registry. Enable user units with systemctl --user enable --now. Edge cases that still bite teams include Swarm, Compose profiles, GPU passthrough needing rootful mode, and rootless host networking differences.

Yes, with three paths and different trade-offs. Option A is podman-compose via pip install, which works for simple multi-container dev stacks but some Compose v3 features lag Docker Compose v2. Option B preserves the exact docker compose CLI by enabling podman.socket, setting DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock, then running docker compose up -d backed by Podman. Option C is production sidecars where you rarely compose the PHP app itself. Test volume paths, network aliases, and healthcheck blocks because Podman respects OCI health checks but rootless networking behaves differently from Docker defaults.

Laravel Sail assumes Docker Desktop or dockerd, so Docker remains the smoothest local path. If you must use Podman, set DOCKER_HOST to the Podman socket and install podman-docker so Sail's docker calls reach Podman. On Linux bare metal, bind-mounted vendor/ performance is usually acceptable. On podman machine on macOS, bind mounts can be slow, so run composer install inside the container, not on the mounted host path. For production Laravel on EC2, I rarely containerize PHP itself. I containerize Redis, Meilisearch, or queue workers as sidecars alongside Apache and PHP-FPM on the host, which Podman fits well.

Rootless Podman writes bind-mounted directories as the mapped host UID, often 100999, while PHP-FPM on the host typically runs as www-data. Logs and cache files become unwritable even when the site returns HTTP 200. I hit this on sister sites sharing a Deployer 7 pipeline where rootful Docker worked locally but rootless Podman failed silently after deploy. Three fixes work in practice: run the container with --userns=keep-id so container UID matches host UID, pre-create directories with chown to the mapped UID on the host, or use a named volume instead of a bind mount for writable paths. Always test write paths, not just HTTP responses.

Rootless containers cannot bind host ports 1 through 1024 by default because unprivileged users lack permission to listen on privileged ports. Two practical fixes exist. Set sysctl net.ipv4.ip_unprivileged_port_start=80 so unprivileged processes may bind from port 80 upward. Alternatively, keep Nginx or Apache on the host as reverse proxy terminating TLS and forward to the container on 8080 or another high port. On legal-tech portals I often run Apache on the host and keep app containers on 8080+ anyway, which avoids the rootless port trap entirely and matches patterns used on production Laravel infrastructure.

Podman has native systemd integration unlike Docker, which often needs hand-written unit files. Run podman generate systemd --name redis --files --new, move the generated unit to ~/.config/systemd/user/, then systemctl --user daemon-reload and enable the service. On modern Podman, Quadlet is cleaner: drop a .container file in ~/.config/containers/systemd/ with Image, PublishPort, and AutoUpdate=registry keys, then reload user systemd. Pair AutoUpdate=registry with health checks before trusting auto-updates on payment webhook workers. Without loginctl enable-linger on deploy users, user systemd units stop when SSH sessions close and production containers die on logout.

Podman pods group containers that share a network namespace, similar to Kubernetes pod semantics. Create a pod with podman pod create --name app-stack -p 8080:80, then run nginx and your API with --pod app-stack so they communicate over localhost without overlay DNS. This helps when you front an API with Nginx inside the same pod. Docker Engine does not have an equivalent native concept. For service discovery across separate containers, Compose networks or Netavark with slirp4netns or pasta still apply. Host Nginx or Traefik typically terminate TLS in front regardless of pod layout.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: