How to Migrate From Docker Desktop to Podman: Step-by-Step (2026)

Nov 10, 2026 · 13 min · Ajay Kumar

This guide is for developers and platform teams who want to replace Docker Desktop with Podman on a laptop, usually because the company has crossed Docker's licence thresholds, without breaking everyone's docker compose up. At the end you will have Podman running in a VM on macOS or Windows (or natively on Linux), your existing images and named volumes moved across, the docker CLI and Docker Compose talking to Podman's socket, and the two classic rootless problems (low ports and bind-mount permissions) fixed. Commands were checked against Podman 6.1.4 (12 October 2026), Podman Desktop 1.30.5 and Docker Compose 5.7.0. You need admin rights on the machine and about 6 GB of free RAM for the VM. If you are still deciding whether to move at all, read the companion post, Podman vs Docker in 2026, first.

One platform warning before you start. Podman 6.0 removed support for Intel Macs and Windows 10. Podman Desktop still installs Podman 5.8 on Intel Macs, and that series gets security fixes only until the second week of June 2027. If your fleet includes either, plan around that date.

Docker Desktop to Podman: the order that avoids data loss 1. Inventory and export docker save, volume tarballs 2. Install Podman 6.1.4 or Podman Desktop 1.30 3. Create the VM podman machine init, start 4. Wire up the socket mac-helper, DOCKER_HOST, context 5. Load your data podman load, volume import 6. Test, then uninstall compose up, then remove Desktop Linux host: skip step 3 and run systemctl --user enable --now podman.socket instead
The migration order used in this guide. Export before you install or uninstall anything, because uninstalling Docker Desktop deletes its VM and every image and volume in it.

Step 1: Inventory and export what lives in Docker Desktop

Docker Desktop keeps images, containers and volumes inside its own VM. The uninstaller removes that VM, so anything you have not exported is gone. Start by listing what you have and deciding what is worth keeping. Most images can simply be pulled again; local-only builds and named volumes with data (a dev database, say) are what you need to save.

# What is running, what images are local-only, what volumes hold data
docker context ls
docker ps -a --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>'
docker volume ls

mkdir -p ~/podman-migration && cd ~/podman-migration

# Save local images you cannot re-pull (one archive, several images)
docker save -o images.tar myapp:dev myworker:dev

# Export each named volume as a tarball using a throwaway container
for v in pgdata redisdata; do
  docker run --rm -v "$v":/data -v "$PWD":/backup docker.io/library/alpine:3.24 \
    tar -C /data -cf "/backup/$v.tar" .
done
ls -lh

Stop any database containers before exporting their volumes, or take a proper dump (pg_dump) instead, because a tarball of a running database is not a consistent backup. Then quit Docker Desktop, but do not uninstall it yet. Keeping it around for a week gives you a fallback.

Step 2: Install Podman or Podman Desktop

Pick one: the CLI alone, or Podman Desktop, which bundles Podman and walks you through creating a machine. For a team rollout I recommend Podman Desktop, since it also handles the Docker compatibility socket.

# macOS (Apple silicon): the Podman project recommends its pkg installer over Homebrew
#   CLI only:   podman-installer-macos-arm64.pkg from
#               https://github.com/podman-container-tools/podman/releases/tag/v6.1.4
#   Desktop:    the .dmg from https://podman-desktop.io/downloads
#   Homebrew (community-maintained, not recommended by upstream):
brew install --cask podman-desktop

# Windows 11 (PowerShell). Podman 6 requires WSL 2 or Hyper-V
wsl --update
wsl --install --no-distribution
winget install RedHat.Podman-Desktop

# Fedora
sudo dnf -y install podman
# Ubuntu 22.04+ / Debian 12+ (distribution packages may lag behind 6.1.4)
sudo apt-get update && sudo apt-get -y install podman

podman --version
# expected: podman version 6.1.4

The pkg installer puts binaries in /opt/podman/bin and runs podman-mac-helper install for you, which matters in Step 4. On Windows, the Podman for Windows guide lets you pick WSL or Hyper-V during installation. On Linux, distribution packages are often a release or two behind; check podman --version and note that anything below 6.0 still has the old CNI and slirp4netns defaults.

If you are leaving Docker Desktop, you still want the docker CLI and Compose binaries, since your scripts call them. On macOS, Homebrew's docker formula is the CLI only (no daemon):

brew install docker docker-compose
# Compose is a CLI plugin; tell the docker CLI where Homebrew put it
mkdir -p ~/.docker
cat ~/.docker/config.json 2>/dev/null   # check before overwriting
# Add this key to ~/.docker/config.json:
#   "cliPluginsExtraDirs": ["/opt/homebrew/lib/docker/cli-plugins"]
docker compose version
# expected: Docker Compose version v5.7.0

Step 3: Create and start a Podman machine (macOS and Windows)

Containers need a Linux kernel, so on macOS and Windows Podman runs a Fedora CoreOS VM called a machine. On Apple silicon the default provider in Podman 6 is libkrun; on Windows it is WSL unless you chose Hyper-V. Size it for your heaviest Compose stack, not for hello-world.

# 4 vCPUs, 8 GiB RAM, 100 GiB disk; rootless by default
podman machine init --cpus 4 --memory 8192 --disk-size 100
podman machine start

Expected output, trimmed and illustrative (not captured from a run):

Starting machine "podman-machine-default"
This machine is currently configured in rootless mode. If your containers
require root permissions (e.g. ports < 1024), or if you run into compatibility
issues with non-podman clients, you can switch using the following command:

	podman machine set --rootful
...
Machine "podman-machine-default" started successfully

Your home directory is mounted into the VM at the same path by default ($HOME:$HOME, per podman-machine-init(1)), so bind mounts from project directories under ~ work without extra configuration. Projects outside your home directory need an extra --volume at init time.

Should you go rootful? If your Compose files publish ports below 1024 or mount the Docker socket into containers, a rootful machine is the path of least resistance on a laptop, because the VM is already the isolation boundary. You can switch later:

podman machine stop
podman machine set --rootful
podman machine start

On Linux there is no machine. Podman runs natively, and you enable the API socket instead (Step 4).

Step 4: Point the docker CLI and Docker Compose at Podman

Every Docker-API client (the docker CLI, Compose, IDE plugins, Testcontainers) looks for a socket. You have three ways to give them one; use the first that works for your platform.

macOS, default socket. If podman-mac-helper is installed (the pkg installer and Podman Desktop's Docker compatibility toggle both do it), /var/run/docker.sock is a symlink to the machine's Podman socket and nothing else is needed.

ls -la /var/run/docker.sock
# expected, roughly: lrwxr-xr-x ... /var/run/docker.sock -> /Users/you/.local/share/containers/podman/machine/podman.sock

# If it is missing (Homebrew install), install the helper and restart the machine
sudo /opt/podman/bin/podman-mac-helper install    # Homebrew: use $(brew --prefix)/bin/podman-mac-helper
podman machine stop && podman machine start

docker info --format '{{.ServerVersion}}'
# expected: 6.1.4   <- the Podman version, so docker is talking to Podman

Any platform, DOCKER_HOST or a context. Ask Podman where its socket is and point clients at it. A Docker context is tidier than an environment variable because it survives new shells and you can switch back.

# macOS
SOCK=$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')
# Linux (rootless): enable socket activation first
systemctl --user enable --now podman.socket
SOCK=$(podman info --format '{{.Host.RemoteSocket.Path}}')   # /run/user/1000/podman/podman.sock

docker context create podman --docker "host=unix://$SOCK"
docker context use podman
docker version --format 'client {{.Client.Version}}, server {{.Server.Version}}'

Windows. Podman listens on the default npipe:////./pipe/docker_engine pipe when the machine starts, so the Docker CLI usually works immediately. If a tool complains, set $env:DOCKER_HOST="npipe:////./pipe/podman-machine-default" in PowerShell, using the path from podman machine inspect --format '{{.ConnectionInfo.PodmanPipe.Path}}', as the Podman Desktop docs describe.

Remove the Docker Desktop leftovers that break the CLI. The most common is "credsStore": "desktop" in ~/.docker/config.json, which points at a credential helper that disappears with Docker Desktop. Delete that line (or change it to osxkeychain after brew install docker-credential-helper) and log in to your registries again.

Step 5: Load images and import volumes

Now move the data from Step 1. podman load reads docker save archives directly, and podman volume import unpacks a tarball into an existing volume.

cd ~/podman-migration
podman load -i images.tar
podman images

for v in pgdata redisdata; do
  podman volume create "$v"
  podman volume import "$v" "$v.tar"
done
podman volume ls

One naming difference trips people up here. Docker assumes docker.io for short names like nginx; Podman resolves short names through unqualified-search-registries in registries.conf and, depending on short-name-mode, may prompt you to pick a registry. Use fully qualified names (docker.io/library/nginx:1.30.6-alpine) in Dockerfiles, Compose files and scripts and the question never comes up.

Step 6: Run your Compose files and fix rootless problems

With the socket wired up, run your stack exactly as before.

docker compose up -d        # real Docker Compose 5.7.0, Podman engine
docker compose ps
# or let Podman pick a provider; pin it to avoid surprises
export PODMAN_COMPOSE_PROVIDER=docker-compose
podman compose up -d

podman compose is a wrapper that runs an external provider, docker-compose first and podman-compose second, as its man page explains. Pinning it with PODMAN_COMPOSE_PROVIDER means everyone on the team runs the same thing.

Two problems account for most failures on rootless setups (Linux, or a rootless machine).

Ports below 1024. Rootless processes cannot bind privileged ports. The Podman troubleshooting guide shows the error as Listen failed for HOST TCP port */80: Permission denied. Either publish a high port (8080:80), go rootful on a Mac or Windows machine, or lower the threshold on Linux:

echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman-ports.conf
sudo sysctl --system

Permission denied on bind mounts. In a rootless container, root maps to your user and every other UID maps to a subordinate range, so an image that runs as postgres or UID 1000 may not be able to write your files. The fixes, from podman-run(1): --userns=keep-id maps your UID to the same UID inside the container; the :U volume option chowns the source to the container's user; and on SELinux hosts (Fedora, RHEL) add :Z for a private label or :z for a shared one.

# compose.yaml fragments that work under rootless Podman 6
services:
  api:
    image: docker.io/library/node:24-alpine
    volumes:
      - ./src:/app/src:Z              # SELinux private label (no-op on macOS)
    ports:
      - "8080:8080"                   # high port: no sysctl needed
  db:
    image: docker.io/library/postgres:18.6-alpine
    volumes:
      - pgdata:/var/lib/postgresql    # imported in Step 5
volumes:
  pgdata:
    external: true

With Docker Compose talking to the socket, the cleanest way to get keep-id without editing every file is the server-side default in containers.conf(5), which the Podman service applies to containers it creates. (podman-compose also accepts userns_mode: keep-id per service.)

# ~/.config/containers/containers.conf (rootless Podman on Linux)
[containers]
userns = "keep-id"

Test containers that reach the host: Podman adds both host.containers.internal and host.docker.internal to /etc/hosts, so existing configs keep working. For Testcontainers on a rootless setup, Podman Desktop's tutorial sets DOCKER_HOST and TESTCONTAINERS_RYUK_DISABLED=true.

Once a week passes without anyone reaching for the old install, uninstall Docker Desktop with /Applications/Docker.app/Contents/MacOS/uninstall on macOS, or Start-Process 'Docker Desktop Installer.exe' -Wait uninstall on Windows. Keep ~/.docker, since the CLI still reads its config and contexts from there.

Step 7 (Linux servers): replace restart policies with Quadlet

On a Linux host, the real gain is running long-lived containers as systemd services. Drop a .container file in ~/.config/containers/systemd/ and systemd's generator creates the unit. The full syntax is in podman-quadlet-basic-usage(7); this is the minimum for a database with the volume you imported.

# ~/.config/containers/systemd/db.container  (Podman 6.1.4)
[Container]
Image=docker.io/library/postgres:18.6-alpine
ContainerName=db
Volume=pgdata:/var/lib/postgresql
Environment=POSTGRES_PASSWORD_FILE=/run/secrets/pg
Secret=pg
PublishPort=127.0.0.1:5432:5432

[Install]
WantedBy=default.target
printf '%s' "$(openssl rand -hex 16)" | podman secret create pg -
systemctl --user daemon-reload
systemctl --user start db.service
loginctl enable-linger "$USER"     # keep it running after you log out
systemctl --user status db.service --no-pager

Do not run systemctl enable on a Quadlet unit; it fails with "Unit is transient or generated". The [Install] section is what makes it start at boot. The systemd service troubleshooting guide covers what to do when the unit itself fails.

Troubleshooting

Symptom Likely cause Fix
Cannot connect to the Docker daemon at unix:///var/run/docker.sock Machine stopped, or podman-mac-helper not installed podman machine start; install the helper or use a podman Docker context
error getting credentials ... docker-credential-desktop: executable file not found Docker Desktop's credsStore left in ~/.docker/config.json Remove "credsStore": "desktop" or set osxkeychain, then docker login again
Listen failed for HOST TCP port */80: Permission denied Rootless cannot bind ports below 1024 Use a high port, podman machine set --rootful, or set net.ipv4.ip_unprivileged_port_start
Permission denied writing to a bind mount UID mapping or SELinux label --userns=keep-id, :U, or :Z on the volume
Prompt to choose a registry, or short-name resolution enforced errors in CI Unqualified image name Use docker.io/library/... style names everywhere
Rootless containers stop when you log out (Linux) No systemd lingering loginctl enable-linger $USER
Failed to enable unit: Unit ... is transient or generated systemctl enable on a Quadlet unit Add [Install] WantedBy=default.target and daemon-reload instead
Bind mounts empty or path not found on macOS Project lives outside $HOME Recreate the machine with --volume /path:/path
docker compose not found after removing Docker Desktop Compose plugin path unknown to the CLI Add cliPluginsExtraDirs to ~/.docker/config.json or call docker-compose
Testcontainers hangs on Ryuk Rootless Podman Set TESTCONTAINERS_RYUK_DISABLED=true and DOCKER_HOST

FAQ

Can I run Docker Desktop and Podman side by side?

Yes, and for the first week you should. They run separate VMs and keep separate image stores. The only conflict is /var/run/docker.sock on macOS, which only one of them can own, so use Docker contexts (docker context use podman or desktop-linux) to switch deliberately.

Do I need to rewrite my Dockerfiles or compose.yaml?

Usually not. Podman builds Dockerfiles with Buildah and runs Compose files through the real Docker Compose. The changes people make are fully qualified image names, high ports or rootful mode, and keep-id (in containers.conf or podman-compose) or :U for bind mounts that need write access.

Does Podman Desktop cost anything for companies?

No. Podman and Podman Desktop are Apache-2.0 with no seats. Docker Desktop is free only for companies under 250 employees and $10 million in revenue, plus personal, education and non-commercial open source use.

What about Intel Macs?

Podman 6 does not support them. Podman Desktop installs Podman 5.8 on Intel Macs, which receives CVE and critical fixes until the second week of June 2027, so treat those machines as a time-limited exception.

When I move a team, the first thing I check is not Podman at all: it is every Compose file that mounts /var/run/docker.sock or publishes a port below 1024, because those are the two things that turn a one-hour migration into a week. I give a few volunteers the Podman Desktop route with a pinned PODMAN_COMPOSE_PROVIDER, keep Docker Desktop installed until their stacks have run cleanly for a week, and only then write the internal doc. On Linux servers I go straight to Quadlet. The official references are the Podman documentation, the Podman Desktop migration docs and the Podman 6.0 release notes for everything that changed.

Related: Podman vs Docker in 2026: Podman 6.1, Rootless, Quadlet and Licences, How to Fix Docker 'No Space Left on Device': Prune, Logs, data-root (2026) and How to Reduce Docker Image Size: Multi-Stage Builds Guide (2026).

Spot an error or something out of date? Email me and I will fix it and log it on the corrections page.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related