最可靠的方式是优先读取/proc/1/cgroup匹配docker、kubepods等关键词;失败时fallback至/proc/1/environ解析container=字段;再失败则检查/proc/mounts中的overlay文件系统,/.dockerenv仅作辅助不可依赖。

读取 /proc/1/cgroup 是最可靠的第一步
宿主机的 PID 1(通常是 systemd 或 init)其 cgroup 路径一般为 / 或 /init.scope;而容器中 PID 1 的 cgroup 路径必然暴露运行时特征,比如含 docker、kubepods、lxc、crio、podman 等关键词(大小写不敏感)。这是内核直接暴露的事实,无法伪造,也不依赖特权或外部命令。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
fopen("/proc/1/cgroup", "r")打开,失败(返回nullptr)时直接跳过,不报错 - 逐行读取(
fgets或std::getline),每行转小写后用find()查找子串,避免正则(Alpine 镜像常无 C++11<regex>) - 匹配任意一行即可,无需解析层级——
/docker/、/kubepods.slice/、/lxc/都算有效信号 - Linux 5.16+ 的 cgroup v2 模式下,首行可能是
0::/,此时该文件无法提供标识,需 fallback
fallback 到 /proc/1/environ 解析 container= 字段
/proc/1/environ 是二进制块,以 \0 分隔各环境变量,不能当普通文本处理。Docker 默认注入 container=docker,Podman 可能写 container=podman,但该字段非强制——distroless、rootless 或定制镜像常缺失。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
open("/proc/1/environ", O_RDONLY)打开,失败(-1)时直接忽略 - 用
std::vector<char></char>一次性读入全部内容,再遍历每个\0分隔的字符串 - 对每个字符串用
substr(0, 10) == "container="判断(C++20 可用starts_with()) - 不要用
std::getline(..., '\0')—— 标准库不保证支持空字符分隔
权限受限时查 /proc/mounts 中的 overlay 文件系统
当 /proc/1/cgroup 和 /proc/1/environ 均因 seccomp、read-only 挂载或 rootless 权限被拒时,唯一可退路是检查挂载点。Docker、Podman、Kubernetes 默认使用 overlay、overlay2 或 fuse-overlayfs,这些在 /proc/mounts 中可见。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 打开
/proc/mounts,逐行扫描是否含"overlay"、"overlay2"或"fuse-overlayfs" - 注意区分误报:
/var/lib/docker/overlay2是路径,不是文件系统类型;要匹配第3列(type 字段) - 若
open()失败或read()返回EPERM,说明沙盒极强,此时可结合/proc/self/status中capeff: 0000000000000000和seccomp: 2辅助判断
/.dockerenv 只能作为辅助线索,不可依赖
/.dockerenv 仅由 Docker daemon 创建,Podman、LXC、CRI-O、distroless 镜像均不生成;且有人会手动创建该文件伪装环境。它存在只能说明“可能是 Docker”,不存在则完全不能排除容器环境。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
access("/.dockerenv", F_OK)快速试探,0 表示存在,-1 表示不存在或无权限 - 结果为 true 时,仅作加分项,不改变主判断逻辑
- 某些只读启动的 Alpine 容器可能连
/proc/1/cgroup都不可读,但/.dockerenv存在——这种边缘 case 不值得单独分支处理
/proc/1/cgroup、/proc/1/environ、/.dockerenv 全部不可访问,唯一残留线索只有 /proc/mounts 和 /proc/self/status。这类环境虽小众,但正是检测逻辑必须兜底的地方。


















