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

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, the implementation calls dockerLikeList("docker", "docker") to execute docker ps against the Docker Engine API. The Podman runtime in 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 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 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. 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:


# 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:

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 and runtime_podman.go, while Kubernetes uses crictl in runtime_crictl.go and LXC parses lxc-ls JSON in runtime_lxc.go.
  • Container data is normalized into the ContainerMatch struct defined in 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →