How witr Detects and Handles Container Runtimes: Docker, Podman, Kubernetes, and LXC Explained

witr discovers containerized processes by parsing cgroup files on Linux (/proc/<pid>/cgroup) for runtime-specific markers, then resolves human-readable names through runtime CLIs; on non-Linux platforms, it falls back to command-line flag inspection.

The witr process monitor provides a unified view of system processes regardless of whether they run natively or inside containers. This article examines how the pranshuparmar/witr source code implements detection and handling for Docker, Podman, Kubernetes, LXC, LXD, and Incus runtimes.

Linux Container Detection via cgroup Inspection

On Linux systems, witr identifies container membership by examining each process's cgroup hierarchy. The implementation in internal/proc/process_linux.go reads /proc/<pid>/cgroup and scans for runtime-specific substrings.

The recognized markers include:

  • docker — Docker containers
  • podman — Podman containers
  • kubepods — Kubernetes pods
  • containerd — containerd runtime
  • colima — Colima containers
  • lxc.payload — LXC/LXD containers

When a marker is found, extractContainerID or extractLXCBasedContainerName extracts the container identifier. The resolveContainerName function then invokes the appropriate runtime CLI to convert this ID into a human-readable name.

// Simplified flow from process_linux.go (lines 54-96)
func ReadProcess(pid int) (*ProcessInfo, error) {
    cgroupData, _ := os.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pid))
    
    if strings.Contains(string(cgroupData), "docker") {
        id := extractContainerID(cgroupData)
        name := resolveContainerName("docker", id)
        return &ProcessInfo{ContainerID: id, ContainerName: name}, nil
    }
    // ... additional runtime checks
}

Non-Linux Fallback: Command-Line Parsing

On macOS, Windows, and BSD systems where /proc is unavailable, witr uses detectContainerFromCmdline in internal/proc/container_detect.go. This function parses process command lines for known runtime flags such as --name, -p, and --profile to infer container membership.

Docker and Podman: Docker-Compatible Runtime Handling

Both Docker and Podman leverage shared infrastructure in internal/proc/runtime_dockerlike.go, with lightweight registration files for each runtime.

Runtime Registration

Each runtime registers itself via init() functions:

// runtime_docker.go (lines 5-13)
func init() {
    RegisterRuntime("docker", &dockerRuntime{})
}

// runtime_podman.go (lines 5-13)
func init() {
    RegisterRuntime("podman", &podmanRuntime{})
}

Container Listing with dockerLikeList

The dockerLikeList function executes <binary> ps --no-trunc --format '{{.ID}}|{{.Names}}|...' and parses the output into model.ContainerMatch structs:

// runtime_dockerlike.go (lines 35-91)
func dockerLikeList(binary string) ([]model.ContainerMatch, error) {
    cmd := exec.Command(binary, "ps", "--no-trunc", 
        "--format", "{{.ID}}|{{.Names}}|{{.Image}}|{{.Status}}")
    // ... parses pipe-delimited output into ContainerMatch slice
}

Host PID Resolution

To map a container to its host process ID, dockerLikeHostPID runs:

docker inspect -f '{{.State.Pid}}' <container_id>

Implementation: runtime_dockerlike.go lines 39-48.

Metadata Enrichment

The dockerLikeEnrich function fetches precise start timestamps via:

docker inspect -f '{{.State.StartedAt}}' <container_id>

Implementation: runtime_dockerlike.go lines 50-71.

Kubernetes: crictl Integration

For Kubernetes environments, witr uses crictl rather than Docker commands.

Runtime Registration

// runtime_crictl.go (lines 13-18)
func init() {
    RegisterRuntime("k8s", &crictlRuntime{})
}

JSON-Based Container Operations

Unlike Docker's formatted strings, crictl outputs JSON:

// runtime_crictl.go (lines 20-56)
func (r *crictlRuntime) List() ([]model.ContainerMatch, error) {
    cmd := exec.Command("crictl", "ps", "-o", "json")
    // ... unmarshals into crictlContainerList structure
}

Host PID extraction (lines 59-62) calls crictl inspect <id> and reads Info.Pid from the resulting JSON. Enrichment (lines 64-90) extracts command lines, mounts, and refined start times from the same inspect payload.

LXC, LXD, and Incus: LXD-Like Runtime Family

These three runtimes share a common implementation pattern in internal/proc/runtime_lxdlike.go.

Individual Runtime Registration

Runtime Registration File Lines
LXC runtime_lxc.go 13-15
LXD runtime_lxd.go 15-17
Incus runtime_incus.go 12-14

Shared LXD-Like Operations

The lxdLikeList function executes <binary> list --format json and parses the JSON array into ContainerMatch objects (runtime_lxdlike.go, lines 42-50).

Host PID retrieval (lxdLikeHostPID, lines 52-70) extracts State.Pid from the container state JSON. Enrichment adds network and mount information through formatLXDLikeNetworks and formatLXDLikeMounts (lines 77-102).

Core Orchestration: Container Runtime Registry

The internal/proc/container_runtime.go file provides the central coordination layer for all container operations.

Runtime Availability Checking

Each runtime's Available() method uses exec.LookPath to verify the binary exists:

// runtime_dockerlike.go (lines 84-87)
func (r *dockerRuntime) Available() bool {
    _, err := exec.LookPath("docker")
    return err == nil
}

Resolution and Deduplication

The ResolveContainer function iterates all registered runtimes, gathers matches, and deduplicates by runtime|id combination (container_runtime.go, lines 24-48).

Enrichment Orchestration

EnrichContainer (lines 15-31) delegates to runtime-specific enrichment methods, adding ports, mounts, and precise start times to a ContainerMatch after initial identification.

Practical Usage Examples

// List all containers visible to witr
matches := proc.ListAllContainers()
for _, c := range matches {
    fmt.Printf("%s (%s) – %s\n", c.Name, c.Runtime, c.ID)
}
// Resolve the host PID of a Docker container named "myapp"
pid := proc.ResolveContainerHostPID("docker", "myapp")
if pid > 0 {
    fmt.Printf("Host PID: %d\n", pid)
}
// Enrich a single container match with full metadata
match := &model.ContainerMatch{Runtime: "k8s", ID: "abcd1234"}
proc.EnrichContainer(match)
fmt.Printf("Full command: %s\nStarted at: %v\n", match.Command, match.StartedAt)

Summary

  • Linux detection relies on /proc/<pid>/cgroup parsing with runtime-specific substring matching in process_linux.go.

  • Non-Linux platforms fall back to command-line inspection via container_detect.go.

  • Docker and Podman share the dockerLike* helper family in runtime_dockerlike.go for listing, PID resolution, and enrichment.

  • Kubernetes uses crictl with JSON-based operations in runtime_crictl.go.

  • LXC, LXD, and Incus share lxdLike* helpers in runtime_lxdlike.go for consistent JSON-based handling.

  • Central orchestration in container_runtime.go provides registration, availability checking, deduplication, and enrichment dispatch.

Frequently Asked Questions

How does witr determine if a process runs inside a container?

On Linux, witr reads /proc/<pid>/cgroup and searches for runtime-specific strings like docker, kubepods, or lxc.payload according to internal/proc/process_linux.go. On macOS, Windows, and BSD, it parses command-line arguments for container runtime flags via detectContainerFromCmdline.

What container runtimes does witr support?

witr supports Docker, Podman, Kubernetes via crictl, LXC, LXD, and Incus according to the pranshuparmar/witr source code. Each runtime registers through init() functions in dedicated files like runtime_docker.go and runtime_crictl.go.

How does witr resolve container names from process IDs?

After extracting a container ID from cgroup data, witr calls resolveContainerName which invokes the appropriate runtime CLI (docker, podman, crictl, etc.) to convert the ID to a human-readable name as implemented in process_linux.go.

Can witr map a container back to its host process ID?

Yes. Each runtime implements a HostPID method: Docker/Podman use docker inspect -f '{{.State.Pid}}', Kubernetes uses crictl inspect reading Info.Pid, and LXC/LXD/Incus read State.Pid from JSON list output.

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 →