最直接有效的方式是将配置文件所在路径以:ro显式挂载,让内核在VFS层拦截所有写操作——哪怕容器内进程拥有root权限也无法覆盖或删除;必须只读的包括Nginx/Apache主配置、证书私钥、应用启动参数三类文件;推荐使用长语法volumes:-type:bind source:./conf target:/etc/myapp read_only:true。
只读挂载不是加个 :ro 就完事
很多人以为在 volumes 后面加个 :ro 就算完成只读保护,结果服务一启动就报错退出。根本原因在于:应用是否真能“只读运行”——它会不会偷偷往配置目录写临时文件、生成 pid、创建 socket 或修改权限位?这些行为在只读挂载下都会被内核拦截,返回 read-only file system 错误。这不是权限问题,是文件系统层的硬性拒绝。所以第一步永远是确认行为,而不是直接上锁。
精准识别哪些路径该设为只读
别凭感觉划范围,按实际用途分类处理:
- /etc/config、/etc/nginx、/usr/local/etc:纯配置加载路径,99% 场景下可安全设为只读
- /usr/share/nginx/html、/var/www/static:静态资源目录,无运行时写入需求,强烈建议只读
- /certs、/keys、/secrets:证书与密钥文件,必须只读,防止被进程意外覆盖或 chmod 修改
- 避开 /var/log、/tmp、/run、/data:这些路径天然需要写入,强行只读等于给应用戴手铐
两种写法效果一样,但推荐用长语法
短语法简洁,但易遗漏上下文;长语法显式声明,便于团队协作和后期维护:
短语法(够用但不推荐长期维护):
volumes:<br> - ./conf:/etc/nginx:ro<br> - ./html:/usr/share/nginx/html:ro
长语法(清晰、可扩展、支持更多选项):
volumes:<br> - type: bind<br> source: ./conf<br> target: /etc/nginx<br> read_only: true<br> - type: bind<br> source: ./html<br> target: /usr/share/nginx/html<br> read_only: true
绕过只读限制的合理方式:tmpfs + 显式可写卷
当应用坚持要写某些路径(比如 Nginx 要写 /var/cache/nginx 或 /var/run/nginx.pid),不要妥协把整个挂载点设为可写。正确做法是:
- 用
tmpfs挂载内存临时目录:tmpfs:<br> - /var/cache/nginx<br> - /var/run
- 对日志等持久化内容,单独挂载可写命名卷:
volumes:<br> - nginx-logs:/var/log/nginx
- 若需保留部分子目录可写(如
/etc/nginx/conf.d允许动态注入),则拆分挂载:- ./conf/nginx.conf:/etc/nginx/nginx.conf:ro<br>- ./conf/conf.d:/etc/nginx/conf.d:rw
三步验证只读是否真正生效
光看 compose 启动成功不够,得动手测:
-
进容器尝试写文件:
docker-compose exec web sh -c 'echo test > /etc/nginx/nginx.conf 2>/dev/null || echo "✅ 只读生效" -
查挂载参数:
docker-compose exec web mount | grep '/etc/nginx'—— 输出应含ro,字样(注意逗号) -
检查进程行为:启动后立刻查日志
docker-compose logs web,重点找Permission denied、Read-only file system、failed to create类错误,它们会暴露你漏掉的隐式写入路径
常见失败场景与对应解法
以下错误不是配置错了,而是没理解应用行为:
-
Nginx 启动失败,报
open() "/var/run/nginx.pid" failed (13: Permission denied)→ 缺少tmpfs: [/var/run]或未挂载可写/var/run -
Java 应用崩溃,日志里出现
java.io.IOException: Read-only file system→ 检查 JVM 参数是否指定了-Djava.io.tmpdir到只读路径,应改到/tmp或挂载tmpfs:/tmp -
PHP-FPM 报
failed to create /var/run/php/php-fpm.sock→/var/run/php必须可写,建议tmpfs: [/var/run/php] -
容器启动后立即退出,
docker-compose ps显示Exit 1→ 一定是某个初始化脚本或入口命令触发了写操作,用docker-compose run --rm --entrypoint sh web进去手动执行启动命令复现问题


















