Docker Compose with Podman
Enable `podman.socket`, set `DOCKER_HOST`, and the genuine Docker Compose binary brings up a two-service stack on Podman. Then `docker compose down` hits the rootless netns error, exits 1, and leaves a container behind.
Registries and Remote Hosts Guide 36 of 47 Intermediate
- OSUbuntu 26.04 LTS (resolute)
- Podman5.7.0
- Runtimecrun 1.21
- Networknetavark 1.16.1
- TimeAbout 15 min
- Reviewed22 August 2026
Written against the versions above. Podman follows the distribution here rather than a vendor repository, so the version you get is the one Ubuntu shipped. The commands are stable across 5.x.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| PODMAN01 | 192.168.0.24 | Ubuntu 26.04 LTS | Primary Container Host | 2 Core | 4 GB | 50 GB |
Before you start
- guide 4 - that guide ended at exactly the point this one starts: Compose runs, and has nothing to talk to.
-
Nothing is listening yet
podman.socketisdisabledand the socket file does not exist. This is the state guide 1 found immediately after installing, and it is deliberate - a Docker-compatible API on by default would undo half the point of being daemonless.It is worth being clear about what enabling it does and does not mean. The socket is socket-activated: systemd holds the listening socket, and
podman system serviceis started only when something connects and exits again afterwards. So you are not starting a daemon - you are agreeing that one may be started on demand.bash Example session systemctl --user is-enabled podman.socketdisabled[exit 1]ls -l $XDG_RUNTIME_DIR/podman/podman.sockls: cannot access '/run/user/1000/podman/podman.sock': No such file or directory[exit 2]Expected result
disabled, andNo such file or directory.Success conditionYou have confirmed there is no API socket before you create one.
-
Enable it, and prove it speaks Docker
systemctl --user enable --now podman.socketand the socket appears at$XDG_RUNTIME_DIR/podman/podman.sock- under/run/user/1000, owned by you. Rootful Podman puts it at/run/podman/podman.sockinstead, and which one a tool finds decides which Podman it drives.The proof that it is Docker-compatible is to talk to it as Docker would:
curl --unix-socket $XDG_RUNTIME_DIR/podman/podman.sock http://d/v1.41/versionA Docker API path -
/v1.41/version- answered by Podman. That is the whole compatibility story: not a translation layer around the CLI as in guide 4, but an implementation of Docker's HTTP API. Which is why it works for tools rather than only for people typing commands.bash Example session systemctl --user enable --now podman.socketCreated symlink '/home/sysadmin/.config/systemd/user/sockets.target.wants/podman.socket' → '/usr/lib/systemd/user/podman.socket'.systemctl --user is-active podman.socketactivels -l $XDG_RUNTIME_DIR/podman/podman.sockls: cannot access '/run/user/1000/podman/podman.sock': No such file or directory[exit 2]curl -s --unix-socket $XDG_RUNTIME_DIR/podman/podman.sock http://d/v1.41/version{"Platform":{"Name":"linux/amd64/ubuntu-26.04"},"Components":[{"Name":"Podman Engine","Version":"5.7.0","Details":{"APIVersion":"5.7.0","Arch":"amd64","BuildTime":"2026-02-23T15:35:32Z","Experimental":"false","GitCommit":"","GoVersion":"go1.25.0","KernelVersion":"7.0.0-30-generic","MinAPIVersion":"4.0.0","Os":"linux"}},{"Name":"Conmon","Version":"conmon version 2.1.13, commit: unknown","Details":{"Package":"conmon_2.1.13+ds1-2_amd64"}},{"Name":"OCI Runtime (crun)","Version":"crun version 1.21\ncommit: 10269840aa07fb7e6b7e1acff6198692d8ff5c88\nrundir: /run/user/1000/crun\nspec: 1.0.0\n+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +WASM:wasmedge +YAJL","Details":{"Package":"crun_1.21-1ubuntu3_amd64"}}],"Version":"5.7.0","ApiVersion":"1.41","MinAPIVersion":"1.24","GitCommit":"","GoVersion":"go1.25.0","Os":"linux","Arch":"amd64","KernelVersion":"7.0.0-30-generic","BuildTime":"2026-02-23T15:35:32Z"}Expected resultAn active socket unit, a socket file you own, and a JSON version response from a Docker API path.
Success conditionA Docker API request was answered by Podman.
-
Point Compose at it
An ordinary
compose.yaml- two services, a published port, a named volume. Nothing Podman-specific in it.One environment variable connects the two:
DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock docker compose up -dAnd it works exactly as it would against Docker - creating a network, a volume and two containers, in dependency order:
Network stack_default Created Volume stack_site Created Container stack-web-1 StartedThen check with Podman's commands.
podman pslistsstack-web-1andstack-cache-1;podman volume lsshowsstack_site. Compose created real Podman objects, and both tools see the same things - because there is one store, not a Docker-compatible shadow copy.curlgetshttp=200. Anddocker compose psreports the stack as Compose understands it, so Compose's own state tracking works too.bash Example session cd ~/stack && cat compose.yamlservices: web: image: docker.io/library/nginx:alpine ports: - "8093:80" volumes: - site:/usr/share/nginx/html cache: image: docker.io/library/redis:alpinevolumes: site:cd ~/stack && DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock docker compose up -d>>>> Executing external compose provider "/usr/libexec/docker/cli-plugins/docker-compose". Please see podman-compose(1) for how to disable this message. <<<< Network stack_default Creating Network stack_default Created Volume stack_site Creating Volume stack_site Created Container stack-cache-1 Creating Container stack-web-1 Creating Container stack-cache-1 Created Container stack-web-1 Created Container stack-cache-1 Starting Container stack-web-1 Starting Container stack-cache-1 Started Container stack-web-1 Startedpodman ps --format 'table {{.Names}} {{.Image}} {{.Status}}'NAMES IMAGE STATUSstack-cache-1 docker.io/library/redis:alpine Up Less than a secondstack-web-1 docker.io/library/nginx:alpine Up Less than a secondpodman volume lsDRIVER VOLUME NAMElocal stack_sitecurl -s -o /dev/null -w 'http=%{http_code}\n' http://localhost:8093http=200cd ~/stack && DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock docker compose ps>>>> Executing external compose provider "/usr/libexec/docker/cli-plugins/docker-compose". Please see podman-compose(1) for how to disable this message. <<<< NAME IMAGE COMMAND SERVICE CREATED STATUS PORTSstack-cache-1 docker.io/library/redis:alpine "redis-server" cache 2 seconds ago Up 1 secondstack-web-1 docker.io/library/nginx:alpine "nginx -g daemon off;" web 2 seconds ago Up 1 second 80/tcpExpected resultCompose creating a network, volume and two containers, all visible to
podman ps.Success conditionA Compose file is running under Podman.
-
And the teardown is where it shows
docker compose downdoes not come back clean:Container stack-cache-1 Error while Stopping Error response from daemon: removing container ... network: * rootless netns: kill network process: permission denied Error: executing /usr/libexec/docker/cli-plugins/docker-compose down: exit status 1Same rootless netns failure as guide 23, and worse here.
podman kube downprinted the error and finished the job; Compose exits 1 andpodman ps -astill showsstack-cache-1behind. The teardown genuinely did not complete.That is the honest summary of this compatibility route: the API is good enough that real tools work, and the seams show at teardown. For interactive use it is fine - re-run
down, orpodman rm -af. For a CI pipeline that checks exit codes it is a problem, and the fix is not to use Compose there: a Quadlet unit (guide 40) orpodman kube playis what to reach for when something has to be scripted.Also note
DOCKER_HOSThad to be set on every invocation. Export it in your shell profile if you use this regularly, and remember it makes every Docker-speaking tool on the machine talk to Podman - which is usually what you want and occasionally a surprise.bash Example session cd ~/stack && DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock docker compose down>>>> Executing external compose provider "/usr/libexec/docker/cli-plugins/docker-compose". Please see podman-compose(1) for how to disable this message. <<<< Container stack-web-1 Stopping Container stack-cache-1 Stopping Container stack-web-1 Stopped Container stack-web-1 Removing Container stack-cache-1 Error while Stopping Container stack-web-1 RemovedError response from daemon: removing container 2b34bcc8c996b9f5cde6d6de2cf6d6745f7a0d4e08d6a97d7d1a686398fa80c6 network: 1 error occurred: * rootless netns: kill network process: permission deniedError: executing /usr/libexec/docker/cli-plugins/docker-compose down: exit status 1[exit 1]podman ps -a --format 'table {{.Names}} {{.Status}}'NAMES STATUSstack-cache-1 Exited (0) Less than a second agoExpected resultA stopping error, exit status 1, and one container still listed afterwards.
Success conditionYou know what this route does badly as well as what it does well.
Troubleshooting
Cannot connect to the Docker daemon at unix:///var/run/docker.sock.Why:
DOCKER_HOSTis not set, so Compose looked in Docker's default place.Fix:
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock. Confirm the file exists first.The socket works while you are logged in and not from cron or at boot.
Why: A user socket needs the user manager running.
Fix:
sudo loginctl enable-linger $USER- the same fix as guide 41.A tool reports an unsupported Docker API version.
Why: Podman implements a specific API version and some clients pin higher.
Fix:
curl --unix-socket ... http://d/v1.41/versionshows what is served; most clients allow an override.docker compose downleaves containers behind.Why: The netns error in step 4 aborts the teardown.
Fix:Run it again, or
podman rm -afandpodman volume prune. Check exit codes rather than assuming success in a script.