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 containerspodman— Podman containerskubepods— Kubernetes podscontainerd— containerd runtimecolima— Colima containerslxc.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>/cgroupparsing with runtime-specific substring matching inprocess_linux.go. -
Non-Linux platforms fall back to command-line inspection via
container_detect.go. -
Docker and Podman share the
dockerLike*helper family inruntime_dockerlike.gofor listing, PID resolution, and enrichment. -
Kubernetes uses
crictlwith JSON-based operations inruntime_crictl.go. -
LXC, LXD, and Incus share
lxdLike*helpers inruntime_lxdlike.gofor consistent JSON-based handling. -
Central orchestration in
container_runtime.goprovides 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →