Collecting rootless Podman logs with Elastic Agent: the CRI parser, user-scoped paths, and the Podman socket
Rootless Podman containers write their logs in CRI format. This Fleet policy reads them and attaches container.* fields, with the match_source_index value that rootless paths need.
Elasticsearch turns raw logs into structured, searchable data at ingest. Follow the collect and analyze logs tutorial to see it end-to-end. Start a free cloud trial or try Elastic on your local machine now.
Rootless Podman exposes a Docker-compatible API, but its logs are a different matter. They use CRI format, they live under the user's home directory, and the API socket is per-user. The Elastic Docker integration is built for Docker's JSON logs and its /var/lib paths, so a Custom Logs integration is the right input here. Collecting rootless Podman logs with Elastic Agent needs three settings: format: cri on the container parser, a file path into the user's overlay-containers storage, and add_docker_metadata pointed at the rootless Podman socket with match_source_index: 7. The default of 4 is correct for Docker's shorter path.
This post builds that pipeline from a real debugging session and ends with Agent Builder diagnosing a Jellyfin playback failure from the enriched logs. It also covers the alternatives, journald and the OpenTelemetry Collector, and when to prefer them.
How rootless Podman logs differ from Docker logs
The Docker integration reads Docker's format, paths and socket, and rootless Podman uses different ones for all three. Each produced a distinct symptom during our debugging session.
The log format is CRI, not Docker JSON
Podman's default file-based log driver is k8s-file, and even --log-driver=json-file is just an alias for it.
Instead of Docker's one-JSON-object-per-line format, Podman writes the format used by Kubernetes container runtimes (CRI): a timestamp, the stream name, a partial/full flag, and then the message.
An example follows:
2026-08-31T09:15:04.518084921+02:00 stdout F 10.89.0.2 - - [31/Aug/2026:07:15:04 +0000] "GET / HTTP/1.1" 200 615A parser expecting Docker JSON breaks on this immediately.
Where rootless Podman stores container logs
Rootful Docker writes logs under /var/lib/docker/containers/<container-id>/. Rootless Podman stores everything under the user's home instead:
/home/<user>/.local/share/containers/storage/overlay-containers/<container-id>/userdata/ctr.logAny integration with a hardcoded /var/lib/... path finds nothing.
The Podman API socket is per-user
In rootless Podman there is no /var/run/docker.sock. Instead, it exposes a Docker-compatible API on a user-scoped socket, typically /run/user/<uid>/podman/podman.sock.
Where Elastic Agent runs: host install vs containerized
This walkthrough assumes Elastic Agent is installed directly on the host, not in a container. In our case, the Agent is Fleet-managed and installed directly on a Ubuntu host. It is not running in a container. That matters because the paths in the Agent Policy are resolved by the Agent process itself: a containerized Agent only sees its own mount namespace, so the host directories above would have to be volume-mounted into the Agent container and referenced by their in-container paths. Running as root with default privileges is also what lets the Agent read files under another user's home directory.
How the rootless Podman log pipeline fits together
The setup we ended up with is simple once all the pieces are known:
- Elastic Agent tails the raw CRI log files from the rootless user's container storage.
- A
containerparser withformat: cristrips the timestamp, stream, and flag prefix from each line. - A processor extracts the container ID from a fixed position in the log file path.
- The
add_docker_metadataprocessor resolves that ID against the rootless Podman socket and attaches container name, image, and labels. - Enriched events are shipped to Elasticsearch.
Let's now build the log ingestion pipeline step by step.
Step 1: Confirm the Podman log driver
First, check which log driver Podman uses on your host, because the default varies by distribution and containers.conf:
podman info --format '{{ .Host.LogDriver }}'If it prints journald, either switch to the file-based driver or jump to the journald alternative at the end of this post. To make k8s-file the default for all containers of this user, set it in ~/.config/containers/containers.conf:
[containers]
log_driver = "k8s-file"You can also set it per container with podman run --log-driver=k8s-file. Remember that json-file is accepted but silently behaves as k8s-file: do not expect Docker-formatted JSON from it. Containers pick up the driver at creation time, so you will need to recreate any containers that were started with a different driver.
Step 2: Enable the rootless Podman API socket
The metadata enrichment in step 4 needs the Podman API. Enable the user-scoped socket as the user that runs the containers:
systemctl --user enable --now podman.socket # without sudo!
loginctl enable-linger $USERThe enable-linger call keeps the user's systemd instance, and with it the socket, alive when the user is not logged in. Verify the socket answers Docker-compatible API calls:
curl --unix-socket /run/user/$(id -u)/podman/podman.sock \
http://d/v1.41/containers/jsonYou should get a JSON array describing the running containers. This compatibility layer is exactly why a processor named add_docker_metadata will work against Podman later.
Step 3: Collect rootless Podman logs with a Custom Logs integration
In Fleet, add the Custom Logs (Filestream) integration to your agent policy instead of the Docker integration.
Set the file path pattern to the rootless storage location:
/home/<user>/.local/share/containers/storage/overlay-containers/*/userdata/ctr.logThen, in the advanced options, configure the parser so the agent understands the CRI format:
- container:
format: criThe container parser removes the <timestamp> <stream> <flag> prefix, reassembles partial lines (the P flag marks a message that was split), and stores the original timestamp and stream in the event.
For standalone agents, the equivalent input configuration looks like this:
- type: filestream
id: rootless-podman-logs
data_stream:
dataset: podman.container_logs
paths:
- /home/<user>/.local/share/containers/storage/overlay-containers/*/userdata/ctr.log
parsers:
- container:
format: criAt this point you should see clean log messages in Discover, but without any container context: no name, no image, no labels. A raw container ID buried in log.file.path is all you have, which makes the data hard to filter and nearly impossible to correlate.
Step 4: Enrich with container metadata using add_docker_metadata
The add_docker_metadata processor enriches our logs with information such as the container name. It extracts a container ID from the log file path, queries the Docker-compatible API for that container, and attaches the container's metadata to each event.
Add it to the integration's processors field (or under processors: in a standalone input):
- add_docker_metadata:
# Replace `1000` with the UID of the user running the containers (`id -u <user>`).
host: "unix:///run/user/1000/podman/podman.sock"
match_source: true
match_source_index: 7The match_source_index value deserves an explanation, as it is not a commonly used one. The processor splits the log file path on /, discards the empty leading element, and picks the component at the given index as the container ID.
For the rootless Podman path, the indices work out like this:
Index | Component |
|---|---|
0 |
|
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
The default value of 4 exists because Docker's path is /var/lib/docker/containers/<container-id>/..., where index 4 lands on the ID. However, as you can see from the table above, in our case the proper value is 7.
Note that the correct index depends on the depth of the home directory. /home/<user-id>/... puts the ID at index 7, but a nonstandard home location shifts it. Count the components of your actual path to verify whether 7 is the right value for you.
Once the ID resolves correctly, the processor calls the Podman socket and each event gains the familiar container.* fields. Here is a real document from a Jellyfin container managed with podman-compose:
The metadata is more complete than it first appears: alongside container.id and container.image.name, every label on the container arrives too, including the com_docker_compose_* and io_podman_compose_* labels set by podman-compose and the standard org_opencontainers_image_* labels baked into the image. Filtering all logs of a compose project is now one query on container.labels.com_docker_compose_project.
Step 5: Validate the rootless Podman log pipeline
Generate some traffic and check Discover:
podman run -d --name nginx-demo -p 8080:80 nginx
curl localhost:8080A healthy pipeline produces events where:
messagecontains only the application log line, with no CRI prefix.@timestampmatches the timestamp Podman wrote.log.file.pathpoints intooverlay-containers.container.idis the full 64-character ID, andcontainer.nameandcontainer.image.nameare populated.
If metadata is missing but messages parse fine, test the socket with the curl command from step 2 and re-check match_source_index. If messages still carry a timestamp prefix, the CRI parser is not applied; verify the parser YAML made it into the integration policy.
Alternatives for collecting rootless Podman logs
The file-based approach above yields the same ECS container.* fields as the Docker integration, but three alternatives are worth weighing first.
Approach | Metadata | ECS | Throughput | Best when |
|---|---|---|---|---|
Custom Logs + | Name, image, and all labels from the Podman socket | Native | High, plain file tailing | You want the same fields the Docker integration produces |
| Container ID, name, and image as journal fields | Needs a rename step | Lower, rate limited by | Containers run as systemd services via Quadlet |
Docker integration on the compat socket | None for logs | Metrics datasets only | Not applicable to logs | You only need metrics, validated per dataset |
OpenTelemetry Collector ( |
| Partial | High, plain file tailing | You are standardizing on EDOT |
Collect Podman logs with the journald log driver
Podman integrates with systemd natively, and on many distributions journald is already the default log driver. With --log-driver=journald, Podman writes each log line to the journal and attaches CONTAINER_ID_FULL, CONTAINER_NAME, and the image name as structured journal fields.
You can collect these with the Custom Journald logs integration. The metadata comes for free, with no socket, no path counting, and no match_source_index.
However, journald applies rate limiting that can drop bursts from chatty containers unless you raise RateLimitBurst. Throughput is lower than plain file tailing. The container fields also arrive with journald's naming, so you need a rename step (an ingest pipeline or processors) to get ECS-style container.* fields. Rootless containers also write to the per-user journal rather than the system one. An agent running as root still sees those entries, because reading the default journal as root includes user journals, but enable journald persistence (Storage=persistent in journald.conf) if you want container logs to survive a reboot.
If your containers are managed as systemd services via Quadlet, this is a natural fit.
Point the Docker integration at the Podman compat socket (metrics only)
Since Podman exposes a Docker-compatible API, it is tempting to point the Docker integration's host at unix:///run/user/<uid>/podman/podman.sock.
- Metrics datasets can work, because they only talk to the API.
- Logs do not, because the integration reads Docker's JSON log files from Docker's paths, and rootless Podman uses neither. Treat this as a partial option for metrics, and validate each dataset you enable.
Collect Podman logs with the OpenTelemetry Collector
If you are standardizing on OpenTelemetry, for instance by using EDOT (Elastic Distributions of OpenTelemetry) the Collector's filelog receiver reads the same files with its container operator, which auto-detects CRI-style formats:
receivers:
filelog:
include:
- /home/<user>/.local/share/containers/storage/overlay-containers/*/userdata/ctr.log
operators:
- type: container
add_metadata_from_filepath: falseSet add_metadata_from_filepath: false because that option expects Kubernetes pod log paths, which don't match Podman's layout.
Here you will miss out on some data enrichment: there is no Podman equivalent of the k8sattributes processor, so container names and labels are not attached automatically.. You can extract container.id from the file path with a regex operator, but resolving it to names and images requires custom work. The contrib Collector does include a podman_stats receiver for container metrics over the same socket, so a Collector-based setup covers metrics well and logs with reduced metadata.
Parsing application logs from Podman containers
The Custom Logs pipeline above ships raw log lines without application-level parsing. For instance, Nginx logs will end up in your cluster plainly, without proper processing. If you'd like to enable proper processing, you can use Ingest Pipelines to "redirect" specific containers (e.g. nginx) to the Ingest Pipeline defined by the Elastic Integration, and get full parsing of that data "for free".
Root cause analysis on Podman logs with Agent Builder
Was it worth the effort? Absolutely! Getting clean, metadata-rich container logs into Elasticsearch lets an LLM investigate issues against the data directly, and Elastic Agent Builder is the quickest way to see that in action.
Agent Builder, available on Elastic Cloud Serverless and the Enterprise tier for Elastic Cloud Hosted and self-managed, provides a chat interface in Kibana, backed by an LLM, with built-in tools to explore your indices and run ES|QL queries against them.
Here is a real example from the host we just configured. Among other containers, it runs Jellyfin, a media server, as a rootless Podman container. One day, playback of a title fails instantly on an Android TV client: the screen goes black and drops back to the menu, with no error apart from a cryptic "Playback error".
In Discover, filtering on container.name: "jellyfin" narrows the view to a few hundred verbose .NET log lines around the playback attempts. The histogram shows a burst of activity at each failed attempt, but the actual cause is buried somewhere in the noise.
Instead of scanning the messages manually, open the Agent Builder panel directly from Discover and ask it to investigate the issue. The agent queries the log data stream on its own, reconstructs the timeline, and returns a diagnosis:
In this run it worked out that the client requested playback six times. Jellyfin tried to convert a 4K Dolby Vision/HEVC video to H.264 through VA-API hardware transcoding while burning in a subtitle stream, and FFmpeg exited with code 187 each time before producing the first HLS segment. That is why playback stopped at zero milliseconds. It also stated its uncertainty honestly: the available logs don't contain FFmpeg's stderr, so it proposed concrete steps to discriminate between a broken transcoding combination and missing GPU access, including checking whether the container can reach the render device /dev/dri/renderD128.
The pipeline we just built made each step of this investigation possible. The agent can slice by container.name only because add_docker_metadata resolved it from the Podman socket, the message field is queryable because the CRI parser stripped the prefixes, and the timeline is trustworthy because @timestamp comes from Podman, not from ingestion time. Without that work, the LLM would face a pile of prefixed raw lines, likely resulting in worse performance and higher token usage.
Conclusion: three settings for rootless Podman logs
Rootless Podman is Docker-compatible where it matters the most: the API. For log collection it uses its own format, file paths, and socket location, which is what the Elastic Docker integration reads.
Once you know that, the fix is three configuration decisions: parse cri instead of Docker JSON, tail the user's overlay-containers storage, and point add_docker_metadata at the per-user Podman socket with a corrected match_source_index.
The same recipe applies to any rootless user on the host; only the home path and UID in the socket change.
If you want to try this workflow end to end to collect your Podman logs, spin up an Elastic Cloud trial, install the Elastic Agent on your host, and you can go from raw CRI files to fully enriched container logs in one integration policy. If you want to push this further, from a one-off chat to automated investigations that open cases with evidence attached, see automated root cause analysis with Elastic Agent Builder.
How helpful was this content?
Related Content

AI root cause analysis in Elastic Agent Builder that cites its evidence

One edit, every dashboard updated: managing Kibana observability at scale with Terraform

Elastic z/OS ingest: five architectures for mainframe data

