Docker 安全第一课:别拿 root 跑容器
容器里默认是 root,逃逸了就是主机 root。小够讲 Docker 安全的七个基本功:非 root 用户、只读文件系统、最小权限、docker.sock 管控、镜像来源、secrets 管理和日常习惯。
我是小够,只推够用的 VPS。
很多人没意识到:容器里默认就是 root。如果容器有漏洞被逃逸,拿到的就是主机的 root——整台机器沦陷。今天讲七个基本功,不难,但能挡住大多数麻烦。
第一:容器里别用 root
Dockerfile 里加一行,指定运行用户:
FROM nginx:alpine
COPY . /usr/share/nginx/html
USER nginx
官方镜像大多内置了非 root 用户(nginx 镜像里有 nginx 用户)。自己写的镜像,至少做到应用进程不用 root 跑。compose 里也可以指定:
services:
app:
image: myapp
user: "1000:1000"
user: "1000:1000" 是 UID:GID。注意用数字 ID 别用用户名——容器里可能没有这个用户名,数字最稳。自己构建镜像时,可以在 Dockerfile 里建用户:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
第二:文件系统只读
应用不需要写文件?那就整个只读:
services:
web:
image: nginx:alpine
read_only: true
tmpfs:
- /tmp
- /var/cache/nginx
- /var/run
read_only: true 后,攻击者就算进了容器也写不了文件,tmpfs 给必需的临时目录留活路。Nginx 这类静态服务完美适用。需要写数据的地方单独挂数据卷,白名单思路——默认全不让写,需要的再开口子。
第三:最小权限,别给 privileged
# 千万别这么干,除非你知道自己在做什么
docker run --privileged ...
--privileged 等于把主机的所有设备都交给容器,约等于没隔离。需要某个具体权限,用 --cap-add 精确加:
docker run --cap-add NET_ADMIN ...
默认的 capabilities 对大多数应用够用了,缺啥补啥,别一把梭。compose 里:
services:
app:
image: myapp
cap_add:
- NET_ADMIN
cap_drop:
- ALL
cap_drop: [ALL] 先全 drop 掉,再按需加——这是最干净的做法。99% 的 Web 应用连 NET_ADMIN 都不需要,默认配置就是安全的,别手贱加 privileged。
第四:docker.sock 是核按钮
# 只有可信的管理工具(如 Portainer)才配挂这个
volumes:
- /var/run/docker.sock:/var/run/docker.sock
拿到 docker.sock 就等于拿到 Docker 守护进程的控制权,能起任意容器、挂载主机任意目录。除了 Portainer 这类你主动装的管理工具,任何应用镜像都不要挂它,来路不明的一键脚本尤其要检查 compose 文件里有没有这一行。网上抄 compose 文件时,养成扫一眼 volumes 的习惯——小够每次都扫,这已经成肌肉记忆了。
第五:镜像来源要可信
- 优先官方镜像(Docker Hub 上带 Official Image 标的),有 Docker 官方背书,Dockerfile 公开可查。
latest标签的镜像先看 Dockerfile 再决定信不信,Docker Hub 上每个官方镜像的构建历史都是公开的。- 别用"某大神优化版"镜像跑核心服务——你不知道里面加了什么。真要用,先
docker history看层,再决定。省那点时间不值得拿整台机器冒险。 - 镜像签名(Docker Content Trust)是进阶话题,个人用户先做到"来源可信"就够了。
第六:敏感信息别进镜像
密码、API Key 别写进 Dockerfile(ENV 写密码等于把密码打进镜像层,谁拿到镜像谁看到,docker history 都能翻出来)。正确姿势按敏感度排:
- compose 的
.env文件(chmod 600,加.gitignore)——个人用户到这就够。 - 运行时
-e传环境变量,适合临时的。 - 特别敏感的上 Docker secrets(Swarm 模式)或外部密钥管理——个人用户一般用不上,知道有就行。
.env 文件权限设 600,只有自己能读,别 chmod 777 图省事。备份 .env 时记得它也是敏感文件,和备份包一起加密或单独保管。
第七:网络隔离
数据库这类不需要被外网访问的服务,别映射端口到主机。compose 默认网络下,服务之间能互访,外部进不来,这就够了。前面进阶篇讲过 internal: true 网络,数据库放进去,连外网都出不去:
networks:
backend:
internal: true
需要对外服务的只有反代那一个入口,攻击面最小化。记住原则:能不暴露就不暴露,能内网就不公网。
日常习惯
- 定期
docker image prune清掉旧镜像,减少攻击面——旧镜像里的旧软件版本就是已知漏洞。 - 关注镜像更新,基础镜像出安全更新就 rebuild。Watchtower 的通知模式(
--monitor-only)可以帮你盯着。 - 宿主机本身的安全基线别丢:密钥登录、防火墙、自动更新,前面月份都讲过,容器安全是叠加上去的,不是替代。容器再安全,宿主机 SSH 被爆破了也白搭。
- 定期
docker ps看一眼:有没有不认识的容器在跑?有没有本该停掉的还开着?
安全没有一劳永逸,但这七条做了,你已经超过 90% 的个人用户了。剩下的 10% 是职业选手干的事,个人博客不用卷到那份上。
镜像扫描:看看镜像里有没有已知漏洞
Trivy 是个轻量扫描器,扫镜像里的已知 CVE:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image nginx:alpine
输出会列出每个漏洞的严重等级。alpine 版扫出来一般很少,默认版一堆——这也是小够推荐小镜像的原因之一。新镜像上线前扫一遍,养成习惯。注意这个命令挂了 docker.sock,扫完容器就删(--rm),别长期留着。
宿主机基线:容器安全的前提
容器安全是建在宿主机安全上的,宿主机沦陷了容器做什么都没用。基线清单:
- SSH 密钥登录,禁密码登录(前面月份讲过)。
- 防火墙默认拒绝,只放行需要的端口。
- 自动安全更新:
unattended-upgrades开起来,内核漏洞不等你手动。 - 非必要不装:宿主机只跑 Docker,别在上面再装一堆服务——每个服务都是攻击面。
- 定期看登录日志:
lastb | head看看有没有被爆破,fail2ban 装一个自动封 IP。
安全更新流程
# 每月一次,挑个业务低峰期
docker compose pull # 拉新镜像
docker compose up -d # 重建
docker image prune -a # 清旧镜像
更新前备份(老规矩),更新后验证网站正常。基础镜像的安全更新不会提前通知你,每月主动 pull 一次就是个人用户的补丁管理。Watchtower 的通知模式可以帮你盯着,有新版发邮件,你手动决定升不升。