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 vs Docker: Migration Guide

By Kokil Thapa | Last reviewed: August 2026

Moving a production PHP or Laravel stack from Docker to Podman requires more than swapping binaries; it demands understanding architectural differences in daemon management, user namespaces, and networking. This Podman vs Docker: Migration Guide addresses the specific friction points full-stack developers face when adopting rootless containers on Linux servers. While many teams start this transition for security compliance or licensing reasons, the actual migration often stalls on volume permissions and socket compatibility. If you are evaluating infrastructure options for your next project, understanding these operational realities is as critical as selecting the right Laravel development partner who understands modern container orchestration.

How does Podman architecture differ from Docker for PHP applications?

The fundamental difference lies in process ownership. Docker relies on a long-running root-privileged daemon (dockerd) that manages all container lifecycles. Every developer and CI runner interacting with Docker effectively delegates trust to this single root process. Podman eliminates the daemon entirely. Each container runs as a direct child process of the user invoking it, leveraging standard Linux kernel primitives like cgroups v2 and user namespaces without intermediate abstraction layers.

For PHP applications, particularly those handling sensitive legal documents or client data in Nepal's legal-tech sector, this distinction matters operationally. A compromised container in a rootless Podman setup cannot escalate privileges to the host system because the container process never had them. In my experience working on production Laravel applications serving law firms, this isolation simplifies security audits significantly compared to managing Docker's shared daemon socket permissions.

Docker ArchitectureRoot Daemon (dockerd)Container AContainer BContainer CShared Root PrivilegesSingle Point of FailurePodman ArchitectureUser CLISystemdCI RunnerRootless CtrService CtrBuild CtrNo Central DaemonFork/Exec Model
Docker uses a centralized root daemon while Podman forks containers directly from user processes, eliminating shared privilege escalation risks for PHP workloads.

This fork-exec model means there is no background service consuming resources when containers are stopped. For development laptops running multiple Laravel projects simultaneously, this translates to measurable battery and memory savings. However, it also means traditional Docker tooling expecting a persistent API endpoint will fail unless explicitly configured otherwise.

How do you configure rootless containers for Laravel storage permissions?

The most common failure point in any Podman vs Docker: Migration Guide is file permission mismatches. Rootless Podman maps container UID 0 to an unprivileged high-range UID on the host. When your Laravel application writes to /var/www/html/storage, the resulting files may be owned by UID 100999 instead of your development user, breaking subsequent builds or cache clears.

Configuring subordinate UIDs and GIDs

Before running any container, verify your user has allocated subuid/subgid ranges:

$ cat /etc/subuid | grep $(whoami)
kokil:100000:65536

$ cat /etc/subgid | grep $(whoami)
kokil:100000:65536

If these entries are missing, run sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER and log out completely. Without this range mapping, Podman cannot create proper user namespaces and will fall back to less secure modes.

Fixing Laravel storage ownership

In your Dockerfile or entrypoint script, avoid hardcoding UID 1000. Instead, use Podman's --userns=keep-id flag during development to map container UIDs directly to your host user:

podman run --rm -it \
  --userns=keep-id \
  -v ./storage:/var/www/html/storage:Z \
  laravel-app php artisan cache:clear

The :Z suffix is critical on SELinux-enabled systems (RHEL, Fedora, Rocky Linux). It tells Podman to relabel the bind mount so the container can access it. Omitting this causes silent permission denials that look identical to missing directory errors. On Ubuntu systems without SELinux, this flag is harmless but unnecessary.

For production deployments where you want consistent ownership regardless of invoking user, define explicit USER directives in your Dockerfile matching your intended runtime UID. Never run PHP-FPM as root inside containers, whether using Docker or Podman.

What are the key differences between Docker Compose and podman-compose?

While Podman provides a docker alias for single-container commands, multi-service orchestration requires deliberate tooling choices. The ecosystem has matured significantly by 2026, but gaps remain for complex Laravel stacks involving queues, schedulers, and database services.

FeatureDocker Compose v2podman-compose 1.xQuadlet (Systemd)
CLI CompatibilityNative~95% compatibleDeclarative unit files
Rootless NetworkingDefault bridgeslirp4netns/pastaNative systemd networking
Auto-restart Policyrestart: alwaysLimited supportRestart=always
Production SuitabilityHighDevelopment onlyRecommended
GPU PassthroughNative nvidia-dockerExperimentalSupported via units
Secret ManagementDocker secrets/swarmEnv files onlysystemd-creds/load-credential

For local Laravel development, podman-compose works adequately with standard docker-compose.yml files. Install it via pip (pip install podman-compose) rather than distribution packages, which often lag behind upstream releases. Be aware that features like depends_on health checks behave differently; Podman evaluates conditions sequentially rather than maintaining a dependency graph daemon.

For production, abandon compose entirely in favor of Quadlets. These are systemd unit files generated from container definitions, giving you native service management, journal logging, resource limits, and automatic restarts without wrapper scripts. This aligns better with how most Nepal-based hosting providers manage VPS instances anyway.

Start MigrationTarget Environment?Local DevProductionpodman-composeQuadlet Units✓ Fast iteration✓ YAML reuse✗ No auto-restart✓ Native systemd✓ Journal logging✓ Resource limits
Choose podman-compose for local Laravel development and Quadlet systemd units for production deployments requiring reliability and native OS integration.

How do you migrate existing Docker Compose Laravel stacks to Podman?

Migration should be incremental. Do not attempt to convert everything at once. Start with stateless services like Redis or Nginx before touching databases or application containers with complex volume mounts.

  1. Install compatibility layer: Create /usr/local/bin/docker symlink to /usr/bin/podman. Add alias docker-compose='podman-compose' to shell profiles. Test basic commands (docker ps, docker images) before proceeding.
  2. Audit compose file: Remove Docker-specific features unsupported by Podman: runtime: nvidia, swarm-mode secrets, Windows path separators. Replace version: '3.8' header (ignored by both tools now but cleaner to remove).
  3. Configure networking: Rootless Podman uses pasta (or legacy slirp4netns) for port forwarding. Ports below 1024 require either net.ipv4.ip_unprivileged_port_start=80 sysctl or running as root. For Laravel Valet-style setups binding port 80, adjust accordingly.
  4. Test volumes individually: Run each service with --entrypoint /bin/sh and verify mounted paths have correct ownership. Fix permissions using :Z labels or --userns=keep-id before attempting full stack startup.
  5. Validate inter-service DNS: Podman DNS resolution within networks differs subtly. Container names resolve correctly, but aliases defined in compose may require explicit networks.*.aliases entries. Test database connections and queue worker connectivity before declaring success.

On a recent legal-tech portal migration, we discovered that our Elasticsearch container required explicit discovery.type=single-node environment variables under Podman that Docker had previously defaulted. Always validate application behavior, not just container startup status.

When should you choose Podman over Docker for production web systems?

The decision isn't purely technical; it involves team expertise, compliance requirements, and long-term maintenance burden. Having maintained both stacks for clients ranging from Kathmandu law firms to international eCommerce platforms, I've observed clear patterns where each excels.

Podman vs Docker: Production Criteria 2026CriterionPodman AdvantageDocker AdvantageSecurity ModelRootless default, no daemonMature AppArmor/SELinuxLicensingFully open source (Apache 2.0)Desktop subscription requiredEcosystem ToolsGrowing but fragmentedExtensive third-party supportTeam FamiliarityLearning curve for opsIndustry standard knowledgeOS IntegrationNative systemd/cgroups v2Cross-platform consistencyCI/CD PipelinesGitLab native supportGitHub Actions optimized
Podman wins on security and licensing while Docker retains advantages in ecosystem maturity and team familiarity for PHP development teams.

Choose Podman when security compliance mandates rootless operation, when avoiding Docker Desktop licensing fees matters for budget-constrained Nepal projects, or when deep systemd integration simplifies your existing DevOps workflow. For teams already invested in Docker tooling, GitHub Actions, or requiring extensive third-party integrations, staying with Docker remains pragmatic. The best choice depends on your specific operational context, not abstract superiority claims.

For organizations considering this transition alongside broader infrastructure modernization, coordinating with experienced DevOps engineers familiar with Nepal hosting environments prevents costly missteps during migration planning.

Practical Next Steps for Your Podman Migration

Successful adoption of Podman for PHP and Laravel workloads requires methodical validation, not blind replacement. Begin by setting up a parallel development environment alongside your existing Docker stack. Run your test suite against both runtimes for two weeks minimum before decommissioning Docker. Document every deviation you encounter—these become your team's operational playbook.

Prioritize Quadlet conversion for any service currently using restart: always in compose files. The reliability gains justify the initial learning investment. Monitor disk usage carefully; Podman's storage driver defaults differ from Docker's and may consume more space until tuned. Most importantly, treat this Podman vs Docker: Migration Guide as a starting framework adapted to your specific stack, not a universal prescription.

If you're evaluating containerization strategies for a new Laravel project or migrating an existing production system, reach out to discuss your specific requirements. Proper architecture decisions made early prevent expensive rework later, especially when balancing security, performance, and maintainability constraints unique to your business context.

Frequently Asked Questions

Podman is daemonless and rootless by default, while Docker relies on a central root-owned daemon. This makes Podman inherently more secure for multi-tenant Linux servers.

Yes. Install podman-compose or use podman compose (v4.7+). Most v2/v3 YAML works unchanged, but host networking and privileged containers often require syntax adjustments or explicit flags.

Mostly. Podman implements the Docker-compatible CLI and REST API. Aliasing docker=podman works for build, run, ps, and logs, though some Docker-specific flags like --gpus require translation.

Export the image via docker save, import with podman load, then recreate the container using identical environment variables and volume mounts. Test thoroughly before decommissioning the Docker instance to avoid configuration drift during cutover.

Yes. Podman generates Kubernetes YAML natively via podman kube play and podman generate kube. This aligns directly with OpenShift and K8s workflows, unlike Docker which requires external conversion tools or manual manifest writing for cluster deployments.

Rootless Podman maps container UIDs differently. Append :Z or :z to volume mounts for SELinux relabeling, or use --userns=keep-id to match host ownership. Without this, mounted directories appear read-only or empty inside the container despite correct host permissions.

Rootless containers cannot bind privileged ports directly. Configure net.ipv4.ip_unprivileged_port_start=64 in sysctl or use podman run -p 80:8080 with a reverse proxy. On Ubuntu 22/24, adding the user to the appropriate group and setting capabilities also resolves this cleanly.

Build performance is comparable for most workloads. Podman uses Buildah internally, which avoids daemon overhead but lacks Docker’s aggressive layer caching in some edge cases. For CI pipelines on shared EC2 instances, I have seen Podman builds run slightly faster due to lower memory footprint.

Use podman secret create and pass via --secret flag instead of environment variables. Secrets are stored encrypted in the XDG runtime directory and never written to image layers. This is safer than Docker’s .env file approach, especially on shared Nepal hosting environments where multiple users access the same server.

Yes. Configure the GitLab runner executor to use podman instead of docker. Set DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock and ensure the runner user has lingering enabled via loginctl enable-linger. Test pipeline stages individually first, as artifact paths and service containers sometimes need path adjustments.

Podman uses CNI or Netavark instead of Docker’s libnetwork. Recreate networks with podman network create using identical subnets and DNS settings. Container-to-container communication requires explicit network attachment; implicit default bridge behavior differs, so verify inter-service connectivity after migration.

Generate native systemd units via podman generate systemd --new --name myapp. This creates restart policies, dependency ordering, and user-session integration without wrapper scripts. Enable with systemctl --user enable --now container-myapp.service. This is far cleaner than Docker’s restart=always policy for production services on Ubuntu servers.

Podman is fully open-source under Apache 2.0 with no subscription required. Docker Desktop requires paid subscriptions for businesses over 250 employees or USD 10M revenue. For Nepal-based agencies billing in NPR, Podman eliminates ~USD 132/month per developer, saving significant operational costs at scale.

Prometheus node_exporter with podman socket exposure, cAdvisor (with --privileged), or Red Hat’s podman-docker compatibility layer. Standard Docker metrics endpoints work when DOCKER_HOST points to Podman’s socket. Avoid tools requiring Docker Engine API v1.41+ features not yet implemented in Podman 5.x.

Skip migration if your stack depends on Docker Swarm, GPU passthrough via nvidia-container-toolkit, or proprietary Docker extensions. Also avoid switching mid-project if your team lacks Linux fundamentals; rootless debugging requires understanding namespaces, cgroups, and SELinux. Stability matters more than ideological purity in production.

Share this article

Quick Contact Options
Choose how you want to connect me: