# How to Detect Containers with witr Across Docker, Podman, Kubernetes, and LXC

> Learn how to detect containers across Docker, Podman, Kubernetes, and LXC with witr. Discover its runtime-registry pattern for effortless container visibility.

- Repository: [Pranshu Parmar/witr](https://github.com/pranshuparmar/witr)
- Tags: how-to-guide
- Published: 2026-08-09

---

**witr detects containers across Docker, Podman, Kubernetes, and LXC through a runtime-registry pattern where each engine implements a common `runtime` interface that checks binary availability, lists containers, resolves host PIDs, and enriches metadata without requiring manual configuration.**

The open-source tool **witr** (available at `pranshuparmar/witr`) automates container discovery across heterogeneous environments using a pluggable runtime registry. By standardizing how each container engine exposes its inventory, witr can detect containers with witr seamlessly whether you are running Docker daemons, Podman sockets, Kubernetes CRI endpoints, or LXC system containers.

## Understanding the witr Runtime Registry

At the core of witr’s detection capability is an internal `runtime` interface defined in the source tree. Every supported container engine implements this interface with five required methods: `Name()`, `Available()`, `List()`, `HostPID()`, and `Enrich()`.

During package initialization, each runtime calls `registerRuntime()` inside its `init()` function to add itself to a global registry. When you execute a container query, witr iterates over `proc.RegisteredRuntimes()`, filters out engines where `Available()` returns false, and aggregates results into a unified view.

## How Each Container Runtime Is Detected

### Docker and Podman (runtime_docker.go and runtime_podman.go)

Both Docker and Podman reuse a generic *docker-like* helper function. In [`runtime_docker.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_docker.go), the implementation calls `dockerLikeList("docker", "docker")` to execute `docker ps` against the Docker Engine API. The Podman runtime in [`runtime_podman.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_podman.go) mirrors this logic but passes the `"podman"` binary name to the same helper, allowing it to query the Podman socket or CLI.

### Kubernetes via CRI (runtime_crictl.go)

For Kubernetes environments, witr interacts with the Container Runtime Interface (CRI) using the `crictl` command-line tool. The [`runtime_crictl.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_crictl.go) implementation executes `crictl ps` to enumerate pods and containers, then uses `crictl inspect` to resolve host process IDs and metadata. This approach works with any CRI-compliant runtime (containerd, CRI-O) as long as `crictl` is installed and configured.

### LXC (runtime_lxc.go)

The LXC runtime in [`runtime_lxc.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_lxc.go) parses JSON output from `lxc-ls --fancy --format json` to discover system containers. To resolve the host PID for a container, it invokes `lxc-info` and extracts the process ID from the output, enabling witr to map LXC guests to their host namespaces.

## The ContainerMatch Data Model

All runtime implementations return data using the `ContainerMatch` struct defined in [`container.go`](https://github.com/pranshuparmar/witr/blob/main/container.go). This unified model stores:

- **Runtime**: The engine name (e.g., `docker`, `podman`, `crictl`, `lxc`)
- **ID**: The container identifier
- **Name**: Human-readable container name
- **HostPID**: The process ID on the host system
- **State**: Running, stopped, or paused status
- **Image**: Optional image metadata populated by `Enrich()`
- **Network**: Network interface details

The `Enrich()` method in each runtime populates these optional fields by calling engine-specific inspection commands such as `docker inspect`, `podman inspect`, or `lxc-info`.

## Using the witr CLI to Detect Containers

The `witr containers` command drives the runtime registry automatically. You can filter by specific runtimes using the `--runtime` flag:

```bash

# List every container across all detected runtimes

witr containers

# Show only Docker containers

witr containers --runtime docker

# Show only Podman containers

witr containers --runtime podman

# Show only LXC containers

witr containers --runtime lxc

# Show only Kubernetes containers (requires crictl)

witr containers --runtime crictl

```

## Programmatic Container Detection in Go

You can embed witr’s detection logic directly into Go applications by importing the internal packages:

```go
package main

import (
	"fmt"

	"github.com/pranshuparmar/witr/internal/proc"
	"github.com/pranshuparmar/witr/pkg/model"
)

func main() {
	var all []*model.ContainerMatch
	for _, rt := range proc.RegisteredRuntimes() {
		if !rt.Available() {
			continue
		}
		matches := rt.List()
		for _, m := range matches {
			// Resolve host PID if needed
			if m.HostPID == 0 {
				m.HostPID = rt.HostPID(m.ID)
			}
			// Enrich with extra metadata (image, networks, etc.)
			rt.Enrich(m)
			all = append(all, m)
		}
	}

	for _, c := range all {
		fmt.Printf("%s [%s] – PID %d – %s\n", c.Name, c.Runtime, c.HostPID, c.State)
	}
}

```

This pattern loops through all registered runtimes, skips unavailable ones, and aggregates enriched container data into a single slice.

## Summary

- **witr** uses a runtime-registry architecture where each engine registers itself via `registerRuntime()` in `init()` functions.
- The `runtime` interface requires `Available()`, `List()`, `HostPID()`, and `Enrich()` methods to standardize detection across Docker, Podman, Kubernetes, and LXC.
- Docker and Podman share a `dockerLikeList` helper in [`runtime_docker.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_docker.go) and [`runtime_podman.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_podman.go), while Kubernetes uses `crictl` in [`runtime_crictl.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_crictl.go) and LXC parses `lxc-ls` JSON in [`runtime_lxc.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_lxc.go).
- Container data is normalized into the `ContainerMatch` struct defined in [`container.go`](https://github.com/pranshuparmar/witr/blob/main/container.go), providing a unified view of runtime name, IDs, host PIDs, and metadata.
- Both CLI (`witr containers`) and Go APIs (`proc.RegisteredRuntimes()`) support filtering by specific runtimes using the `--runtime` flag or conditional logic.

## Frequently Asked Questions

### How does witr determine which container runtimes are installed?

Each runtime implementation includes an `Available()` method that performs a binary check for the respective CLI tool. For example, the Docker runtime checks for the `docker` binary, Podman checks for `podman`, LXC checks for `lxc-ls`, and the Kubernetes runtime checks for `crictl`. If the binary exists in the system path, the runtime is considered available and included in the detection loop.

### Can I restrict witr to detect containers from only one runtime?

Yes. Use the `--runtime` flag with the `witr containers` command to filter results. Valid values include `docker`, `podman`, `lxc`, and `crictl`. This bypasses the aggregation logic and queries only the specified runtime’s `List()` method, returning a filtered set of `ContainerMatch` objects.

### What is the difference between `List()` and `Enrich()` in witr’s runtime interface?

The `List()` method performs a lightweight query to discover container IDs, names, and basic state (equivalent to `docker ps` or `lxc-ls`). The `Enrich()` method performs a secondary, more expensive inspection to populate optional fields like image names, network interfaces, and detailed status (equivalent to `docker inspect`). This separation allows witr to defer heavy metadata collection until necessary.

### Does witr require root privileges to detect containers?

Privilege requirements depend on the runtime’s socket permissions. Docker and Podman usually require the user to be in the `docker` or `podman` group, or require root access to the Unix socket. `crictl` typically requires root or specific Kubernetes node permissions to access the CRI endpoint. LXC commands usually require root privileges unless the user has specific LXC capabilities configured.