在生产环境中,容器默认可写的根文件系统虽然使用方便,却也允许进程修改配置、写入可执行文件或留下异常数据。将根文件系统设为只读,可以减少误操作和入侵后的持久化空间;与此同时,应用往往仍需写入缓存、PID、Socket、会话文件等临时内容。因此,合理组合只读根文件系统与临时目录挂载,是 Docker 容器加固中非常实用的一步。🔒
一、只读文件系统解决什么问题
启用只读模式后,镜像提供的根文件系统不能在容器运行期间被修改。应用尝试写入未挂载的目录时,会收到“Read-only file system”错误。这能够降低系统文件被篡改、恶意程序落盘以及配置被意外覆盖的风险,但它不是完整的安全方案,仍应配合非 root 用户、最小权限、镜像漏洞扫描和网络访问控制。
使用 Docker CLI 启动容器时,可加入以下参数:
docker run --read-only --name myapp myimage:latest
也可以显式写成:
docker run --read-only=true --name myapp myimage:latest
启用后,可进入容器执行写入测试,例如尝试创建 /test.txt。如果操作失败并提示只读文件系统,说明限制已经生效。需要注意,已单独挂载的卷、绑定目录或 tmpfs 不受根文件系统只读状态的直接限制,其读写能力由各自的挂载选项决定。
二、先找出应用必须写入的目录
不要直接给所有容器统一挂载 /tmp 后就认为配置完成。不同应用的写入路径并不相同,常见位置包括:
- /tmp:通用临时文件、上传处理中间文件。
- /run:PID 文件、Unix Socket 和运行时状态。
- /var/cache/app:应用缓存或编译缓存。
- /var/log/app:必须写入文件的应用日志。
- /var/lib/app:可能包含需要长期保存的业务数据。
可先在测试环境以普通可写模式运行容器,再结合应用日志、启动报错及 docker diff 容器名 检查文件变化。对于业务数据、数据库文件和不能丢失的配置,应使用命名卷或受控的绑定挂载;只有不需要持久化的数据才适合放入 tmpfs。🧭
三、使用 tmpfs 提供临时写入空间
tmpfs 适用于缓存、锁文件、PID 和短生命周期中间文件。它不会写入容器的可写层,容器停止后其中的数据会消失。Docker 官方同时提醒,tmpfs 对应 Linux 内核的临时文件系统,其内容在特定情况下可能进入交换空间,因此不能简单理解为“绝不会接触磁盘”。详细行为可参考 Docker tmpfs 官方文档。
下面的命令将根文件系统设为只读,并为 /tmp 和 /run 提供可写空间:
docker run --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m --tmpfs /run:rw,nosuid,size=16m myimage:latest
其中,rw 表示可写,noexec 禁止从该挂载点直接执行程序,nosuid 禁止利用 setuid 或 setgid 位,size 用于限制最大空间。容量应根据真实负载测试确定,避免临时文件无限增长并挤占宿主机内存。
使用更明确的 mount 写法
Docker 通常推荐使用语义更清晰的 --mount:
docker run --read-only --mount type=tmpfs,destination=/tmp,tmpfs-size=67108864,tmpfs-mode=1777 myimage:latest
tmpfs-mode=1777 适用于需要让多个用户写入的公共临时目录,并保留粘滞位,避免普通用户删除其他用户创建的文件。若应用以固定的非 root UID 运行,则还应验证挂载目录的所有权和权限,防止出现“根文件系统已只读,但应用仍然没有权限写入 tmpfs”的情况。
四、Docker Compose 配置示例
在 Compose 中可以使用 read_only 与 tmpfs 组合配置:
services:
app:
image: myimage:latest
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m
- /run:rw,nosuid,size=16m
如果日志必须长期保留,可将日志目录单独挂载为命名卷;如果应用已将日志输出到标准输出和标准错误,则通常无需开放文件日志目录。持久化数据与临时数据应明确分开,避免把数据库目录误挂到 tmpfs,导致容器重建或停止后数据丢失。📦
五、常见故障与排查方法
- 容器启动即退出:查看 docker logs 容器名,根据报错补充必要的临时目录,而不是取消整个只读限制。
- Permission denied:确认容器运行用户的 UID、GID,以及 tmpfs 的 mode 设置;公共临时目录通常需要正确的写权限和粘滞位。
- No space left on device:检查 tmpfs 容量限制和应用缓存策略,必要时调整 size,但不要无上限地扩大。
- 挂载后原文件消失:tmpfs 会遮蔽目标目录中镜像原有的内容。若应用依赖这些初始文件,应在镜像构建、启动脚本或独立持久卷中妥善准备。
- 数据重启后丢失:这是 tmpfs 的预期行为;需要保留的数据应改用 volume 或 bind mount。
六、上线前的配置建议
推荐原则:根文件系统默认只读,只为经过确认的路径开放写权限;临时数据使用受限 tmpfs,持久化数据使用专用卷。
- 让应用以非 root 用户运行,并验证该用户能写入指定目录。
- 为 tmpfs 设置合理的容量上限,避免影响宿主机内存。
- 在不需要执行程序的临时目录加入 noexec,并优先使用 nosuid。
- 通过健康检查覆盖启动、上传、缓存刷新和日志轮转等真实场景。
- 记录每个可写目录的用途、保存周期和负责人,定期清理不再需要的挂载。
- 将 Compose 配置纳入版本管理,在镜像升级后重新执行只读模式测试。
总结
Docker 只读根文件系统并不是简单添加一个参数,而是一次对应用写入行为的梳理。正确做法是先识别必需的写入路径,再按照数据生命周期选择 tmpfs、命名卷或绑定挂载,并配合容量、权限和安全选项进行约束。完成日志检查、权限测试和重启验证后,这套配置既能增强容器运行时防护,也能让临时数据与持久化数据的边界更加清晰。✅