Docker 容器内不推荐用 root 用户运行

FreeGuideOnline 最新 2026-07-05

dockerfile FROM ubuntu:22.04

安装必要工具并创建用户和组

RUN groupadd --system --gid 1000 appgroup &&
useradd --system --create-home --shell /bin/bash
--uid 1000 --gid appgroup appuser

将应用文件复制到非 root 用户的目录下

WORKDIR /home/appuser/app COPY --chown=appuser:appgroup . .

切换到非 root 用户

USER appuser

CMD ["./my-app"]


关键点:

- `--system` 创建一个系统用户,不会在 `/etc/shadow` 中设置过期信息。
- 使用固定的 UID/GID(例如 1000)便于权限管理。
- `COPY --chown` 确保文件所有权正确,避免运行时权限错误。

### Alpine Linux 的轻量级实现

```dockerfile
FROM alpine:3.18

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /home/appuser/app
COPY --chown=appuser:appgroup . .

USER appuser

CMD ["./my-app"]

adduser -S 创建系统用户,addgroup -S 创建系统组。

运行容器时指定用户

如果你无法修改 Dockerfile,也可以通过命令行或 docker-compose 覆盖运行用户。

命令行方式

docker run --user 1000:1000 your-image

或使用用户名:

docker run --user appuser your-image

注意:容器内必须已存在该用户(或至少存在对应的 UID/GID),否则进程可能因找不到用户而启动失败。如果仅指定数字 UID,容器会以该 UID 运行,但可能缺少家目录或用户环境。

docker-compose 配置

docker-compose.yml 中:

services:
  app:
    image: your-image
    user: "1000:1000"

或使用变量:

user: "${UID:-1000}:${GID:-1000}"

这样可以让容器以宿主机当前用户的身份运行,方便文件权限共享。

从容器的安全降权看 Linux capabilities

即使切换了用户,容器仍可能保留一些危险的 Linux capabilities。你可以进一步缩减容器的能力集合:

docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE ...

当以非 root 用户运行时,大部分不需要的特权能力会被自动限制(因为非 root 用户无权使用它们)。但明确丢弃所有能力仅仅添加必要的(如 NET_BIND_SERVICE,允许绑定特权端口),可以让安全更上一层楼。

处理需要 root 权限的特殊场景

有些容器确实需要部分 root 权限(如需要映射低于 1024 的端口、操作内核模块等)。此时应当:

  • 仍然在内部以非 root 用户启动应用进程,仅在初始化阶段用 root 执行必要操作(例如设置文件权限)。
  • 结合 --cap-add 按需添加能力,而非使用 --privileged
  • 对于需要绑定低端口,可以考虑让应用本身监听高端口(如 8080),然后通过反向代理或 Docker 端口映射将宿主机 80 转发到容器 8080,从而避免 root。
  • 如果必须 root,务必在容器内通过 USERgosusu-exec 临时提权,执行完任务后降回普通用户。

使用 gosu 实现临时提权

FROM debian:bullseye
RUN apt-get update && apt-get install -y gosu
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

entrypoint.sh 示例:

#!/bin/bash
# 作为 root 执行初始化工作
chown -R appuser:appgroup /data

# 切换为 appuser 执行主进程
exec gosu appuser "$@"

这样容器的主进程最终仍然以非 root 用户运行。

检查与验证

运行容器后,可以进入查看当前用户:

docker exec -it <container> id

输出应为 uid=1000(appuser) gid=1000(appgroup) 等,而不是 uid=0(root)

你也可以通过查看进程列表确认:

docker top <container>