# 网站从 www 用户挪进 Docker 以后进程变成了 root，被打穿时损失反而更大吗？

**URL:** <https://www.shiqidu.com/t/topic/1839>\
**Category:** 开发调优\
**Tags:** Linux, 服务器, Docker, 信息安全\
**Created:** [2026 年10 月 29 日 05:22 UTC](https://www.shiqidu.com/t/topic/1839 "2026-10-29T05:22:05Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![shiqidu\_editor](https://www.shiqidu.com/user_avatar/www.shiqidu.com/shiqidu_editor/32/528_2.png) [@shiqidu\_editor](https://www.shiqidu.com/u/shiqidu_editor)\
发布日期： [2026 年10 月 29 日 05:22 UTC](https://www.shiqidu.com/t/topic/1839/1 "2026-10-29T05:22:05Z")

</div>

老服务器上的站往容器里搬，流程一般挺顺：挑个官方镜像，COPY 代码，映射端口，起来就能访问。迁完再看进程，宿主机上原本归 www-data 的 PHP 或 Node，在容器里成了 uid 0，因为镜像没写 `USER` 时默认就用 root。觉得没问题的人说容器隔离更好，反对的人说里面跑 root 等于白给，两边都能讲出道理。

## 比的是运行身份和能碰到的东西

宿主机上用 nologin 的 www 用户跑，应用被打穿后，攻击者拿到的只是一个低权限 shell。想改系统文件、装常驻后门，他还得再找一个本地提权漏洞。这一步未必难，但终归多了道坎。

容器里的 uid 0 默认不做映射，到了宿主机上照样算 0 号用户。Docker 默认只给容器留一小撮 capability，挂载、加载内核模块这类操作都被拒掉，[官方安全文档](https://docs.docker.com/engine/security/)认为入侵者即便在里面拿到 root，也很难造成严重破坏或者逃到宿主。麻烦在后半截：dockerd 默认以 root 身份运行。万一攻击者借内核漏洞或配置失误逃了出来，落地就是宿主机最高权限，www 用户那道提权的坎就直接省掉了。

反过来，容器里改用普通账号跑，被攻破后的处境至少不比宿主机上的 www 用户差，外面还多套了一层命名空间。官方文档的结论也是这个意思：容器默认就相当安全，进程以非特权用户运行时尤其如此。

所以「Docker 和宿主机谁更安全」这个问法有点偏。该看的是进程用哪个 uid 运行，以及它被攻破后能摸到哪些文件、socket 和内核接口。

## 官方建议按这几档来

[userns-remap 文档](https://docs.docker.com/engine/security/userns-remap/)开头写明，防止容器内提权攻击，最好的办法是让应用以非特权用户运行。按改动从小到大排：

- 在 Dockerfile 里建普通用户，再用 `USER` 切过去。[最佳实践页](https://docs.docker.com/build/building/best-practices/)建议显式指定 UID/GID，否则重新构建时分到的号可能会变。临时测试也可以在 `docker run` 时加 `--user`。
- 镜像非得用 root 启动的，在 daemon 上开 userns-remap。容器里的 0 号用户会映射到 `/etc/subuid` 分配的一段高位 UID，这些编号在宿主机上没有任何特权。dockerd 自身仍然跑在 root 下。
- 更进一步是 [rootless 模式](https://docs.docker.com/engine/security/rootless/)，daemon 和容器全都不用 root。按 [UID/GID 映射页](https://docs.docker.com/engine/security/rootless/uid-gid-mapping/)的说明，rootless 下容器里的 root 对应启动 Docker 的那个宿主用户。

Dockerfile 里至少要加这几行：

```dockerfile
FROM debian:bookworm-slim
RUN groupadd -r -g 10001 app \
 && useradd --no-log-init -r -u 10001 -g app app
COPY --chown=app:app ./dist /app
USER app
CMD ["/app/server"]

```

userns-remap 写进 `/etc/docker/daemon.json`，填 `default` 会让 Docker 自动创建 `dockremap` 用户：

```json
{
  "userns-remap": "default"
}

```

保存后执行 `sudo systemctl restart docker`。这个功能有几处限制要提前知道。它不能与宿主机共享 PID 或网络命名空间，`--privileged` 必须搭配 `--userns=host` 才能用。挂载宿主机目录时，文件属主得按映射后的 UID 提前调好，不然容器里写不进去。开启后 `/var/lib/docker` 里原有的镜像和容器会被屏蔽，文档建议在新装的 Docker 上启用。

rootless 想监听 1024 以下端口，需要给 rootlesskit 设置 `cap_net_bind_service` 或者调整 sysctl，前面套一层宿主机 Nginx 反代通常更省事。装好后运行 `docker info`，Security Options 里出现 rootless 字样，说明客户端连上的正是这个无 root 的 daemon。

## 不管选哪档，这几处顺手收紧

- 业务容器别挂 `/var/run/docker.sock`。[docker run 参考](https://docs.docker.com/reference/cli/docker/container/run/)里写着，挂进去就等于让容器完全掌控宿主机上的 Docker daemon。
- 宿主机上的 www 用户别加进 docker 组，[安装后配置页](https://docs.docker.com/engine/install/linux-postinstall/)明确警告这个组会授予 root 级别的权限。
- 根文件系统设为只读，需要写入的目录单独挂 tmpfs 或卷。
- capability 先全部去掉，缺什么再补上，安全文档给的建议也是只留进程明确需要的。

```bash
docker run -d --name web \
  --user 10001:10001 \
  --read-only --tmpfs /tmp \
  --cap-drop=ALL \
  -p 8080:8080 myapp:latest

```

容器起来后执行 `docker exec web id`，看到的实际 uid 比 Dockerfile 里写的更可信。

站里那篇讲 [PHP 老站被打](https://www.shiqidu.com/t/topic/1820) 的帖子说的是怎么堵洞。这篇说的是另一头：洞没堵上、站被打穿以后，运行身份决定了损失有多大。两件事可以一起做。

你们线上的容器里，真正写了 `USER` 的占几成？
