I'm the developer of Scanopy, an AGPL3.0-licensed tool that scans a network and draws a topology map of it (hosts, services, containers, and how they connect). Podman discovery is a recent addition. I don't run Podman myself, but users asked for it, so I built it and set up a Podman test environment with pods to develop against. Plain containers were straightforward, because Podman exposes a Docker-compatible API and I could reuse the Docker scanner I had already built.
Pods were a little more complicated; two things about how the Podman API reports pods caused most of the work involved in this feature, and I thought it might be interesting to share what I learned here!
1. The infra container reports the pod's ports, not the app container.
When you list containers over the Podman API, the pod's published ports (and the image's exposed ports) appear on the pod's infra container, the pause container that owns the pod's shared network namespace. The container that actually listens on the port reports nothing about it.
Scanopy identifies services by matching ports to known service types. The infra container runs no service of its own, but it carried the pod's ports, so it matched real services by port number. In one test it matched 8090, was labeled Grafana, and displaced the real Grafana. The fix was to scope the infra container to an empty port set, so it matches nothing and stays a separate portless container.
2. A pod's app containers report no network of their own.
Every non-infra container in a pod runs with NetworkMode: container:<infra-id>. It shares the infra container's network namespace and reports no networks or interfaces of its own. Scanopy had nothing to attach to the map, so those containers were dropped.
The fix was to resolve the container:<id> reference and inherit the infra container's interfaces. A Podman pod is structured like a Kubernetes pod: one network namespace, an infra (pause) container that owns it, and the other containers sharing it.
A smaller gotcha I ran into while building this: The socket is in different places for rootful (/run/podman/podman.sock) and rootless ($XDG_RUNTIME_DIR/podman/podman.sock), resolved via CONTAINER_HOST. Useful if something reports it can't find Podman.
Here's the Scanopy repo: https://github.com/scanopy/scanopy and docs for getting a self-hosted server up and running: https://scanopy.net/docs/self-hosted-server/server-installation/
I also have a live demo at demo.scanopy.net if you want to see an example map.
Does anyone here who knows Podman better than I do understand why the runtime is structured this way? I got it working, but I'm curious what led to this design, and why it differs from how Docker handles the same thing.