| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64podman-docker-5.8.2-5.el10_2.x86_64 |
| Applies to | Linux hosts where Docker-compatible tools must talk to Podman through a Unix socket or DOCKER_HOST |
| Privilege | Rootless user socket for daily use; rootful socket and podman-docker symlink need administrator access |
| Scope | podman.socket and socket activation, rootless versus rootful socket paths, podman system service, Docker-compatible and Libpod REST endpoints, cautious API version behavior, DOCKER_HOST, docker compose against the socket, RHEL podman-docker and /var/run/docker.sock, external client testing, Swarm API gap, socket security, brief mTLS and in-container socket mounts, boot enablement with linger, and troubleshooting. Does not cover Compose provider selection, full Docker-to-Podman migration, or Podman remote SSH workflows in depth. |
| Related guides | Podman vs Docker Podman architecture Install Podman on Rocky Linux |
Normal podman run does not need a background daemon. That is different from software written for Docker Engine, which often opens a Unix socket and speaks the Docker REST API.
Podman reconciles both models: CLI commands stay fork-exec, and an optional podman.socket unit starts podman system service when a client connects. This guide shows which socket path controls which container store, how to test the API, and what Docker-compatible tools can realistically expect.
What is podman.socket?
Docker-compatible client
│
▼
Unix socket (podman.sock)
│
▼
systemd socket activation
│
▼
podman system service
│
▼
Podman engineThe socket can listen idle until a client connects, then systemd launches the API service. That is how Podman stays daemonless for CLI use while still offering a Docker-compatible REST API for Compose, CI runners, and language SDKs.
You only need the socket when a tool cannot call podman directly — for example when it hard-codes unix:///var/run/docker.sock, reads DOCKER_HOST, or uses a Docker SDK. Day-to-day podman run and podman ps never touch it.
Two REST API layers on one socket
Podman exposes two interfaces on the same Unix socket:
| Layer | Purpose |
|---|---|
| Docker-compatible API | Endpoints Docker clients expect — /v1.40/..., _ping, container and image routes |
| Libpod API | Podman-native routes under /v4.0.0/libpod/... with features Docker API does not cover |
Do not assume every Libpod capability has a Docker API equivalent. Tools that only speak Docker API stay on the compatibility routes.
Docker API version compatibility
Podman documents its compatibility layer against the Docker v1.40 API. The server may accept requests carrying other version prefixes, so the reported ApiVersion does not guarantee complete dockerd feature parity. On Podman 5.8.2, the version JSON can still report broader numbers:
"ApiVersion": "1.44",
"MinAPIVersion": "1.24"Test the endpoints your application actually calls — feature support matters more than the version header alone.
Start the rootless Podman socket
Run these commands as a normal login user — not as root. Enable the user socket:
systemctl --user start podman.socketCheck that systemd is listening:
systemctl --user status podman.socketSample output (tested as user podtest on the lab host):
● podman.socket - Podman API Socket
Active: active (listening)
Listen: /run/user/1014/podman/podman.sock (Stream)
Triggers: ● podman.serviceTypical path:
/run/user/$UID/podman/podman.sockFor example, UID 1000 uses /run/user/1000/podman/podman.sock. The same variable form is:
$XDG_RUNTIME_DIR/podman/podman.sockConfirm the file exists and is owned by your user:
ls -l "$XDG_RUNTIME_DIR/podman/podman.sock"srw-rw----. 1 podtest podtest 0 Aug 23 18:25 /run/user/1014/podman/podman.sockRootless socket operations use that user's Podman image and container store. If you see /run/user/0/... and owner root, you started the user socket as root — that is not a rootless workflow.
Start the rootful Podman socket
System-wide socket for root's Podman store:
sudo systemctl start podman.socketConfirm the rootful unit is listening on /run/podman/podman.sock:
sudo systemctl status podman.socketSample output:
● podman.socket - Podman API Socket
Active: active (listening)
Listen: /run/podman/podman.sock (Stream)Path:
/run/podman/podman.sock| Socket | Podman context |
|---|---|
$XDG_RUNTIME_DIR/podman/podman.sock |
Rootless — containers and images for that user |
/run/podman/podman.sock |
Rootful — root's container store |
A client pointed at the wrong socket sees the wrong containers. Always match DOCKER_HOST to the user context you intend.
Socket activation and podman.service
Socket activation means the API process starts only when a client connects. Check the service unit before any client has connected:
systemctl --user status podman.serviceSample output while idle:
○ podman.service - Podman API Service
Active: inactive (dead)
TriggeredBy: ● podman.socketThat inactive state is normal. Hit the socket with any API client — curl works — and systemd starts the service:
curl --unix-socket "$XDG_RUNTIME_DIR/podman/podman.sock" http://d/_pingRe-check the service unit:
systemctl --user status podman.serviceSample output after activation:
● podman.service - Podman API Service
Active: active (running)
Main PID: 304547 (podman)
└─304547 /usr/bin/podman --log-level=info system serviceWhen idle timeout expires, the service process can exit while podman.socket keeps listening. An inactive podman.service is not a failure if the socket unit is active.
Run podman system service manually
For debugging, run the API service in the foreground:
podman system service --time 0--time 0 disables inactivity shutdown. You can bind a custom socket:
podman system service --time 0 unix:///tmp/podman-test.sockUse systemd socket activation for normal operation — not a manual foreground process left running forever.
Test the API with curl
Docker-compatible health check:
curl --unix-socket "$XDG_RUNTIME_DIR/podman/podman.sock" http://d/_pingOKQuery Docker-compatible version info:
curl --unix-socket "$XDG_RUNTIME_DIR/podman/podman.sock" http://d/v1.40/versionSample output (trimmed):
{"Platform":{"Name":"Red Hat Enterprise Linux 10.2 (Coughlan)"},"Components":[{"Name":"Podman Engine","Version":"5.8.2",...}],"Version":"28.3.3","ApiVersion":"1.44","MinAPIVersion":"1.24",...}The ApiVersion field is what Docker clients read; on this host it reports 1.44 even when you request /v1.40/version. That is why feature testing beats trusting the version string alone.
Libpod ping uses a separate route:
curl --unix-socket "$XDG_RUNTIME_DIR/podman/podman.sock" http://d/libpod/_pingOKLibpod info exposes Podman-native host details:
curl --unix-socket "$XDG_RUNTIME_DIR/podman/podman.sock" http://d/v4.0.0/libpod/infoParse version.Version and host.security.rootless when you need to confirm which store the socket serves.
Configure DOCKER_HOST
DOCKER_HOST is for clients that speak the Docker REST API over a socket — language SDKs, Testcontainers, and other tools that open unix://... and call HTTP routes. It is separate from the podman-docker package, which provides a docker command that invokes Podman directly.
Point Docker API clients at the rootless socket:
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock"For the rootful socket:
export DOCKER_HOST="unix:///run/podman/podman.sock"Verify the socket with curl or a Docker API client — not with docker ps from podman-docker:
curl --unix-socket "$XDG_RUNTIME_DIR/podman/podman.sock" http://d/_pingOKIf docker comes from podman-docker, docker ps exercises the CLI compatibility shim and talks to Podman locally. It does not prove DOCKER_HOST is set correctly. Use curl, the Docker SDK, or another actual Docker API client to verify socket access.
Run Compose with the Podman socket
Compact workflow — provider selection and compat flags live in Podman Compose:
systemctl --user start podman.socketPoint API clients at the same socket your curl tests used:
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock"With podman-docker installed, docker compose is not an independent Docker CLI talking to Podman over the socket. The command path is effectively docker → Podman → podman compose → an external Compose provider such as podman-compose or docker-compose. Podman sets up the environment so that provider can reach the local Podman socket.
docker compose up -dOn this host that invoked podman-compose as the external provider. Verify with Podman:
podman ps --filter name=socktestCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
515b0a2a33a9 docker.io/library/nginx:latest nginx -g daemon o... 1 second ago Up 1 second 0.0.0.0:8092->80/tcp, 8080/tcp socktest_web_1An independently installed Docker-compatible Compose client that reads DOCKER_HOST and calls the REST API directly is a separate compatibility test from this podman-docker wrapper path.
Install and use podman-docker on RHEL
The podman-docker package provides a docker command that calls Podman:
sudo dnf install podman-dockerThe shim reports Podman's version string instead of Docker Engine's:
docker --versionEmulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
podman version 5.8.2On RHEL 10, podman-docker also creates /var/run/docker.sock as a symlink to the rootful /run/podman/podman.sock, allowing Docker API clients that hard-code the traditional Docker socket path to reach Podman:
ls -l /var/run/docker.socklrwxrwxrwx. 1 root root 23 Aug 22 16:11 /var/run/docker.sock -> /run/podman/podman.sockThat symlink targets the rootful socket. For rootless workflows, set DOCKER_HOST to the user socket instead of relying on /var/run/docker.sock. Package layout can differ on other distributions — verify paths on your host.
For CI and automation, document which socket path and which user run the client. Rootless jobs typically need systemctl --user start podman.socket, DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock, and linger enabled when the user is not logged in. Legacy scripts that hard-code /var/run/docker.sock use the rootful socket via podman-docker on RHEL.
Python Docker SDK smoke test
If docker Python package is available, a minimal client uses the same DOCKER_HOST variable:
import docker
client = docker.from_env()
print(client.ping())When DOCKER_HOST points at Podman's socket, ping() should return True — the same _ping route curl exercised. Pin client library versions in CI and test on staging; SDK methods that call Swarm or plugin routes will fail on Podman.
Test Docker-compatible tooling
API compatibility is per application, not universal. On this lab host:
| Tool | Result |
|---|---|
curl _ping and /version |
Works |
Python Docker SDK with DOCKER_HOST |
Works |
docker ps via podman-docker shim |
Works as Podman CLI — does not test DOCKER_HOST |
docker compose up via podman-docker |
Works via Podman Compose wrapper |
/v1.40/swarm |
Not Found |
Testcontainers, language SDKs, and CI runners each need their own staging run. A tool that only creates containers may work; one that calls Swarm, BuildKit-only routes, or dockerd plugins may not.
Common Docker API differences
Keep expectations evidence-based. On Podman 5.8.2 tested here:
- Swarm endpoints return
Not Found - Docker plugin and some BuildKit-specific API paths may be absent or behave differently
- Networking defaults differ from dockerd bridge assumptions
- Container metadata fields may not match Docker byte-for-byte
Reproduce failures against your Podman version before blaming application bugs.
Docker Swarm API is not supported
Docker-compatible container and image routes do not imply Swarm support:
/swarm
/services
/nodesDocker Swarm functionality, including docker stack, is not supported.
Test on this host:
curl --unix-socket "$XDG_RUNTIME_DIR/podman/podman.sock" http://d/v1.40/swarmNot FoundSoftware that schedules services across a cluster needs Kubernetes, OpenShift, or another orchestrator — not Podman's socket API.
Socket security model
The Podman API lets clients perform Podman operations as the socket owner:
| Socket | Effective power |
|---|---|
| Rootless user socket | Container and image control as that user — equivalent to handing them podman CLI access |
| Rootful socket | Podman operations with root privileges |
Filesystem permissions on the socket file are the main local access boundary. Do not make the socket world-writable.
Do not expose an unauthenticated TCP socket
podman system service can listen on TCP, but exposing it without TLS grants remote callers the same power as local socket access:
podman system service --time 0 tcp://0.0.0.0:8080That pattern is unsafe on a network. Prefer Unix sockets locally or SSH port forwarding remotely.
If TCP is unavoidable, use mutual TLS:
podman system service --time 0 \
--tls-cert SERVER_CERT \
--tls-key SERVER_KEY \
--tls-client-ca CLIENT_CA \
tcp://HOST:PORTThis article does not walk through PKI setup — Unix sockets remain the default recommendation.
Mount the socket into another container
Some integrations run a client container that talks to the host Podman socket. Mount the appropriate socket and understand the privilege grant — the inner container gains Podman control at the socket owner's level.
On SELinux-enforcing hosts, a quick test mount may need:
--security-opt label=disableThat disables SELinux labeling for the container and is a security trade-off. Treat socket mounts as handing out container management on the host, not a read-only metrics endpoint.
Enable the socket at boot
Rootless socket for your user:
systemctl --user enable podman.socketFor the socket to survive reboot without an interactive login:
sudo loginctl enable-linger USERNAMELinger keeps the user's systemd manager running at boot. You do not need linger merely to start the socket manually while logged in.
Rootful socket at boot:
sudo systemctl enable --now podman.socketAfter reboot, confirm the socket unit is active (listening) before debugging client tools.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Cannot connect to the Docker daemon | Socket not started or wrong DOCKER_HOST |
systemctl --user start podman.socket; echo "$DOCKER_HOST" |
| Permission denied on socket | Client user mismatch | Run client as socket owner; check ls -l on the .sock file |
| Tool sees root containers, expected rootless | Pointed at rootful socket or /var/run/docker.sock |
Set DOCKER_HOST to $XDG_RUNTIME_DIR/podman/podman.sock |
podman.service inactive |
Socket activation idle timeout | Normal if podman.socket is listening — connect a client to start service |
| API returns Not Found for endpoint | Unsupported route (Swarm, plugins) | Check tool requirements; Swarm needs another orchestrator |
References
- podman-system-service(1) — API service and socket URIs
- podman.socket(5) — systemd socket units
- Podman REST API — Libpod and compatibility routes
- Red Hat — Building, running, and managing containers — socket and
podman-dockeron RHEL
Summary
Podman does not need a daemon for CLI workflows, but Docker-era tools expect a REST API over a Unix socket. podman.socket bridges that gap through systemd socket activation — the service unit may be inactive until a client connects, which is expected behavior.
Rootless and rootful sockets are not interchangeable. $XDG_RUNTIME_DIR/podman/podman.sock serves that user's store; /run/podman/podman.sock serves root's store. Set DOCKER_HOST explicitly, and on RHEL remember that /var/run/docker.sock typically symlinks to the rootful socket via podman-docker.
Test with curl against _ping, /v1.40/version, and Libpod routes before pointing production tools at the socket. Verify DOCKER_HOST with a real Docker API client — not docker ps from podman-docker, which invokes Podman directly. Compose through podman-docker uses Podman's Compose wrapper; Swarm endpoints do not work. Treat socket access as full Podman control at the owner's privilege level — use local Unix sockets, SSH forwarding, or mTLS rather than an open TCP listener.
When a tool fails, capture the exact HTTP path and compare it against what Podman documents for the compatibility layer — reinstalling Podman rarely fixes a missing Swarm or plugin route.
For migration context beyond the socket itself, see Migrate from Docker to Podman. For Compose provider choice, see Podman Compose.

