必须显式挂载 tmpfs,否则 read_only: true 容器无法启动;根本原因是 /tmp、/run、/var/run 等路径需可写以支持临时文件、PID 和 socket,而只读根文件系统会致进程崩溃。

必须显式挂载 tmpfs,否则 read_only: true 容器连启动都失败。 Docker Desktop 本身不改变容器只读机制,它只是运行时载体;真正起作用的是 Compose 文件中对 read_only 和 tmpfs 的组合配置。
为什么 read_only: true 后容器直接退出或报错?
根本原因是:Linux 内核要求 /proc、/sys、/dev 等虚拟文件系统可访问,但更关键的是,绝大多数应用(包括 nginx、supervisord、dotnet 运行时)默认依赖 /tmp、/run、/var/run 这些路径做临时文件、PID、socket 或缓存。一旦根文件系统设为只读,这些路径就不可写,进程立即崩溃或拒绝启动。
常见错误现象包括:
Failed to start daemon: cannot create /var/run/docker.sock: Read-only file systemmkdir /tmp: read-only file system- 应用日志里反复出现
Permission denied但没指明具体路径
tmpfs 挂载必须写在 docker-compose.yml 的 tmpfs 字段下
Docker Desktop 使用标准 Compose v2+ 解析器,不识别 volumes 中带 :rw 的 /tmp 映射——那只是 bind mount,不能解决只读根下的临时目录需求。
正确做法是用 tmpfs 块声明内存挂载点,例如:
services:
app:
image: myapp:latest
read_only: true
tmpfs:
- /tmp
- /run
- /var/run
# 可选:限制大小和权限
# - /tmp:rw,size=16m,mode=1777
注意:
- 每个
tmpfs条目必须是绝对路径,不能带冒号后缀 - 不写
mode时默认为1777(等同于chmod 1777 /tmp),适合多用户场景 -
size=参数建议显式设置,避免内存无节制增长(尤其在 CI 或资源受限环境)
哪些路径必须加 tmpfs?看应用实际行为,不是凭经验猜
别盲目照抄 /tmp、/run。先停掉容器,用 docker exec -it 进去手动检查:
- 运行
ls -l /proc/1/fd | grep -E "(tmp|run|log)"查看 PID 1 进程打开的文件路径 - 查应用文档:比如 .NET 应用会尝试写
Path.GetTempPath(),Python 的tempfile.gettempdir()默认指向/tmp - 如果应用把日志写进
/var/log/app,而该路径是 bind mount 且设了:ro,那就得换掉这个挂载,或改用tmpfs+ 日志轮转到 stdout
典型必须覆盖的路径:
-
/tmp:几乎所有语言运行时临时文件 -
/run和/var/run:守护进程 PID、Unix socket(如/run/nginx.pid) -
/dev/shm:某些 IPC 或大内存共享场景(如 Chromium 渲染进程),按需添加
绑定主机目录替代 tmpfs 的适用边界
tmpfs 数据重启即丢,如果你需要保留调试日志、上传中转文件、或某些缓存,就得用 host bind mount 替代:
- 宿主机目录必须提前创建并
chown到容器内 UID,例如:sudo chown 1001:1001 /host/tmp - Compose 中写法是:
volumes: - /host/tmp:/tmp:rw,**不能**再设read_only: true,否则冲突 - 这种方案牺牲了“纯内存”安全性,但换来数据跨重启存活;生产环境慎用,仅限开发调试或有明确持久化需求的临时路径
最容易被忽略的一点:Docker Desktop 在 macOS 上使用 HyperKit 虚拟机,/host/tmp 实际映射到 VM 内部路径,IO 性能不如原生 Linux,且 chown 必须在 VM 内执行(通过 docker run --rm -v /host/tmp:/mnt alpine ls -l /mnt 验证属主)。











