登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  软件教程

Docker 容器启用只读根文件系统后,临时写目录怎么挂载

来源:17golang原创

时间:2026-10-08 10:19:02 131浏览 收藏

Docker 容器启用只读根文件系统后,不是把整个容器变成“完全不能写”,而是把写权限收缩到明确挂载的目录。最实用的分法是:/tmp、/run、进程锁和短期缓存使用 tmpfs;必须跨容器保留的数据使用 Docker volume;只有宿主机确实需要直接读写文件时才考虑 bind mount。

官方文档:https://docs.docker.com/

推荐组合:--read-only 锁住镜像根文件系统,只为应用真实写目录增加 tmpfs 或 volume。不要为了让程序启动而把整个工作目录重新挂成可写。

步骤1:先盘点应用到底写哪些目录

先把目录分成两类。重启后可以丢失的 PID、Socket、临时文件、上传中间文件和可重建缓存属于临时数据;数据库、用户上传成品、任务结果和状态文件属于持久数据。常见候选如下:

目录示例数据性质推荐挂载
/tmp、/run进程退出后可丢失tmpfs
/var/cache/myapp可重建缓存优先 tmpfs,并限制大小
/var/lib/myapp业务状态或用户数据Docker volume
/etc/myapp配置,运行时不应修改保持只读;必要时只读 bind mount

可见成功状态:每个写目录都被标记为“临时”或“持久”,没有把整个 /var、/app 之类的大目录笼统设为可写。

步骤2:先只启用只读根文件系统,暴露遗漏路径

先用最小配置启动一次,让应用报出真正缺少的写目录。下面的命令故意不添加写挂载:

# 先锁住根文件系统,用启动日志定位应用仍然尝试写入的路径
docker run --rm --read-only --name myapp-check myapp:latest

预期现象是应用正常启动,或明确报告某个目录为 Read-only file system。如果报错路径是日志目录,优先把日志改为标准输出;如果是 PID、Socket 或临时文件,再为那个具体目录配置 tmpfs。

原创容器管理界面中只读根文件系统开关已启用
图1:原创软件界面示意。容器安全设置中只读根文件系统已启用,状态区提示根目录禁止运行时修改;不是 Docker 官方界面截图。

可见成功状态:容器配置显示根文件系统为只读,错误信息只剩下具体写目录,而不是模糊的启动失败。

步骤3:用 tmpfs 挂载 /tmp 与 /run

tmpfs 的内容不会写入容器可写层,容器停止后数据消失,适合临时目录。下面同时限制大小和目录权限:

# 根文件系统保持只读,只开放两个内存临时目录
docker run --rm --read-only --name myapp \
  --mount type=tmpfs,dst=/tmp,tmpfs-size=67108864,tmpfs-mode=1777 \
  --mount type=tmpfs,dst=/run,tmpfs-size=16777216,tmpfs-mode=755 \
  myapp:latest

tmpfs-size 使用字节数;示例分别约为 64 MiB 和 16 MiB。/tmp 常用 1777,但专用目录应尽量改为 0700 或 0770。tmpfs 会占用容器内存额度,并且在宿主机启用交换分区时存在被换出的可能,因此它不是“敏感数据绝不落盘”的绝对保证。

可见成功状态:应用能够创建临时文件,容器停止并重新创建后这些文件不再存在。

步骤4:需要保留的数据改用 volume

如果 /var/lib/myapp 中保存的是业务数据,就不能用 tmpfs。先创建命名卷,再把它挂到唯一需要持久化的路径:

# 创建由 Docker 管理的持久卷
docker volume create myapp-data

# 临时目录使用 tmpfs,业务数据使用命名卷
docker run -d --read-only --name myapp \
  --mount type=tmpfs,dst=/tmp,tmpfs-size=67108864,tmpfs-mode=1777 \
  --mount type=tmpfs,dst=/run,tmpfs-size=16777216,tmpfs-mode=755 \
  --mount type=volume,src=myapp-data,dst=/var/lib/myapp \
  myapp:latest

volume 独立于容器生命周期,删除并重建容器后仍可重新挂载。若镜像在挂载点预置了文件,挂载会遮住该路径原有内容;上线前要确认初始化逻辑,不要把“看不到镜像内文件”误判为文件被删除。

可见成功状态:重建容器后,/var/lib/myapp 的业务数据仍然存在,而 /tmp 中的临时文件已经清空。

步骤5:在 Docker Compose 中固化配置

确认目录边界后,把配置写入 Compose,避免每次手工拼接启动参数:

services:
  app:
    image: myapp:latest
    read_only: true # 根文件系统保持只读
    tmpfs:
      - /tmp:size=64m,mode=1777 # 通用临时文件
      - /run:size=16m,mode=755 # PID、Socket 等运行时文件
    volumes:
      - app-data:/var/lib/myapp # 需要跨容器保留的业务数据

volumes:
  app-data: {} # 使用 Docker 管理的命名卷
# 按 Compose 配置创建或更新服务
docker compose up -d

非 root 进程若仍提示 Permission denied,先核对容器用户的 UID、GID 和目录 mode。不要用 chmod 777 一把放开;应让目录所有者和应用用户匹配,或只赋予目标组写权限。

原创容器部署界面中配置 tmpfs 与命名卷挂载
图2:原创软件界面示意。部署表单将 /tmp、/run 设置为 tmpfs,并把 /var/lib/myapp 设置为持久卷;字段对应 Compose 中的路径、大小和权限。

可见成功状态:服务详情同时显示“根文件系统:只读”、两个 tmpfs 挂载和一个 volume 挂载,容器健康状态正常。

步骤6:做一次正反向写入验证

最终验证要同时证明两件事:根文件系统不能写,显式挂载目录可以写。镜像包含 shell 时可执行:

# 查看实际挂载类型、目标路径和读写属性
docker inspect myapp --format '{{json .Mounts}}'

# /etc 写入应失败;/tmp、/run 与数据卷写入应成功
docker exec myapp sh -c '
  touch /etc/should-fail && echo "异常:/etc 可写" || echo "正确:/etc 只读"
  touch /tmp/tmp-ok && echo "正确:/tmp 可写"
  touch /run/run-ok && echo "正确:/run 可写"
  touch /var/lib/myapp/data-ok && echo "正确:数据卷可写"
'

精简镜像可能没有 shell,此时应通过应用健康检查、专用诊断子命令或测试接口验证,不要为了测试临时给生产镜像安装 shell。Docker inspect 的挂载列表应能区分 tmpfs 与 volume,根文件系统只读状态则由容器配置确认。

原创容器详情界面显示只读根目录与可写挂载验证通过
图3:原创软件界面示意。根目录写入被阻止,/tmp、/run 与数据卷检查通过,挂载类型和健康状态同时可见。

可见成功状态:/etc 的写入失败,三个显式挂载点写入成功;重建容器后临时文件消失、数据卷文件保留。

常见问题

tmpfs 和 volume 应该怎么选?

问自己“容器重建后这个数据必须保留吗”。答案为否时用 tmpfs;答案为是时用 volume。缓存虽然可以重建,但若体积较大或重建成本高,也可以选择 volume,并单独设计清理策略。

为什么挂了 tmpfs 仍然 Permission denied?

只读问题已经解决,但目录权限可能与应用用户不匹配。检查容器 UID、GID 和挂载 mode。多用户共享目录可用组权限,专用目录则优先收紧到应用用户。

可以把整个 /var 挂成可写吗?

不建议。大范围可写会削弱只读根文件系统的价值,还会隐藏镜像中的原始目录。应从错误日志和应用配置中找出最小写路径,一个目录一个目录开放。

tmpfs 会不会把容器内存占满?

有可能。为每个 tmpfs 设置与业务峰值相符的上限,并把它纳入容器内存预算。上传临时文件和缓存尤其需要限额与清理机制,否则应用会从“磁盘写满”变成“内存压力”。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>