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

> Learn how witr detects and handles Docker, Podman, Kubernetes, and LXC runtimes. Discover its cgroup parsing and CLI resolution techniques for process identification.

- Repository: [Pranshu Parmar/witr](https://github.com/pranshuparmar/witr)
- Tags: internals
- Published: 2026-08-10

---

**`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`](https://github.com/pranshuparmar/witr/blob/main/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.

```go
// 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`](https://github.com/pranshuparmar/witr/blob/main/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`](https://github.com/pranshuparmar/witr/blob/main/internal/proc/runtime_dockerlike.go), with lightweight registration files for each runtime.

### Runtime Registration

Each runtime registers itself via `init()` functions:

```go
// 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:

```go
// 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:

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

```

Implementation: [`runtime_dockerlike.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_dockerlike.go) lines 39-48.

### Metadata Enrichment

The `dockerLikeEnrich` function fetches precise start timestamps via:

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

```

Implementation: [`runtime_dockerlike.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_dockerlike.go) lines 50-71.

## Kubernetes: crictl Integration

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

### Runtime Registration

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

```

### JSON-Based Container Operations

Unlike Docker's formatted strings, `crictl` outputs JSON:

```go
// 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`](https://github.com/pranshuparmar/witr/blob/main/internal/proc/runtime_lxdlike.go).

### Individual Runtime Registration

| Runtime | Registration File | Lines |
|---------|-------------------|-------|
| LXC | [`runtime_lxc.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_lxc.go) | 13-15 |
| LXD | [`runtime_lxd.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_lxd.go) | 15-17 |
| Incus | [`runtime_incus.go`](https://github.com/pranshuparmar/witr/blob/main/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`](https://github.com/pranshuparmar/witr/blob/main/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`](https://github.com/pranshuparmar/witr/blob/main/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:

```go
// 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`](https://github.com/pranshuparmar/witr/blob/main/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

```go
// 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)
}

```

```go
// 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)
}

```

```go
// 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`](https://github.com/pranshuparmar/witr/blob/main/process_linux.go).

- **Non-Linux platforms** fall back to command-line inspection via [`container_detect.go`](https://github.com/pranshuparmar/witr/blob/main/container_detect.go).

- **Docker and Podman** share the `dockerLike*` helper family in [`runtime_dockerlike.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_dockerlike.go) for listing, PID resolution, and enrichment.

- **Kubernetes** uses `crictl` with JSON-based operations in [`runtime_crictl.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_crictl.go).

- **LXC, LXD, and Incus** share `lxdLike*` helpers in [`runtime_lxdlike.go`](https://github.com/pranshuparmar/witr/blob/main/runtime_lxdlike.go) for consistent JSON-based handling.

- **Central orchestration** in [`container_runtime.go`](https://github.com/pranshuparmar/witr/blob/main/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`](https://github.com/pranshuparmar/witr/blob/main/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`](https://github.com/pranshuparmar/witr/blob/main/runtime_docker.go) and [`runtime_crictl.go`](https://github.com/pranshuparmar/witr/blob/main/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`](https://github.com/pranshuparmar/witr/blob/main/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.