1 stable release
| new 1.0.0 | May 15, 2026 |
|---|
#13 in #podman-container
19KB
196 lines
detect-container
A small, dependency-free Rust library for detecting whether the current process is running inside a Linux container (Docker, Podman, containerd, Kubernetes, LXC, WSL1, …).
The detection avoids touching paths under /run, /, or any other
location an image author can populate at build time. Every signal it
consults comes from the kernel, the container runtime's cgroup
plumbing, the process's own PID, or /proc/sys/kernel/osrelease.
Important assumption
This crate must not be used to author an init system, embedded
init, initramfs, or anything else that legitimately runs as
PID 1 on the host kernel. The algorithm treats getpid() == 1 as a
positive container signal because, in every realistic application
context, a process being PID 1 means it is the entry point of a
container. If your code might run as PID 1 on bare metal it will
incorrectly report true.
Features
- Zero runtime dependencies.
- Thread-safe; the result is computed once and cached.
- Detects Docker, Podman, containerd, Kubernetes, LXC, WSL1, and similar runtimes.
- Does not consult any user-writable filesystem path
(
/.dockerenv,/run/.containerenv, etc.) — those can be spoofed by a hostile or careless image. - Platform-aware: full detection on Linux, graceful no-op (
false) elsewhere. #![forbid(unsafe_code)].- Optional
diagnosticsfeature for inspecting which checks fired.
Installation
[dependencies]
detect-container = "0.1"
The library import path is detect_container (Rust crate names use
underscores):
use detect_container::is_container;
fn main() {
if is_container() {
println!("running inside a container");
} else {
println!("running on a regular host");
}
}
Detection algorithm
On Linux the following steps are performed in order. The first one that produces a definite answer wins.
- Read
/proc/self/ns/pid's inode and remember it.- If the inode equals
PROC_PID_INIT_INO(0xEFFFFFFC, the hardcoded inode of the root PID namespace since Linux 3.8) → returnfalse. We are in the host's root PID namespace and therefore not in a container.
- If the inode equals
getpid() == 1→ returntrue. See the assumption above: PID 1 outside the host root namespace means we are the container's init.- WSL1 probe — read
/proc/sys/kernel/osrelease; if it containsMicrosoftorWSLbut notWSL2→ returntrue. (The official WSL detection method.) WSL2 is intentionally excluded: it runs a real Linux kernel inside a lightweight Hyper-V VM, so it is a virtual machine rather than a container. - cgroup v1 probe — read
/proc/1/cgroupand/proc/self/cgroup; if either references a known runtime substring (docker,containerd,kubepods,lxc,podman,garden) → returntrue. Useful on older kernels where cgroup paths still encode the runtime name. - Fallback to the cached PID-namespace inode: if it is anything
other than
PROC_PID_INIT_INO(i.e. we are in a child PID namespace) → returntrue. - Otherwise → return
false.
This is intentionally narrower than systemd's
detect_container().
Systemd has to identify which container manager is in use, so it
must consult /.dockerenv, /run/.containerenv,
/run/host/container-manager, /run/systemd/container, and the
process's $container environment variable. We only answer the
yes/no question, so we can rely on signals an image author can't
forge.
On any non-Linux target, is_container() always returns false.
Caching
The result is computed on first use and cached for the lifetime of the
process in a single relaxed AtomicU8. The detection is pure and
idempotent, so the cache uses relaxed atomic operations without
locking; concurrent first calls may each run the detection but will
agree on the result. Subsequent calls are a single relaxed atomic
load.
Diagnostics
Enable the diagnostics feature to inspect every step of the
algorithm without short-circuiting:
[dependencies]
detect-container = { version = "1", features = ["diagnostics"] }
let report = detect_container::diagnostics::report();
println!("in container: {}", report.is_container);
for c in &report.checks {
println!("{:<24} matched={} {}", c.name, c.matched, c.description);
}
A ready-to-run example is included:
cargo run --example report --features diagnostics
This is intended for tests and debugging only; the diagnostics
module is not part of the stable API surface and may change
between minor versions.
Minimum supported Rust version
detect-container requires Rust 1.64 or newer.
License
Licensed under the BSD 2-Clause License. See LICENSE for the full text.