为何docker-compose build生成的文件会写入挂载的数据卷?
现象成因
你之前的认知遗漏了Docker卷的默认初始化规则:
- 镜像构建阶段确实不会挂载任何运行时定义的卷,Dockerfile里所有RUN指令写入的内容只会存储在镜像分层中,不会直接写入宿主机或卷存储空间,这部分认知是正确的。
- 但当一个完全为空的卷(包括首次创建的命名卷、刚生成的匿名卷)第一次被挂载到容器内的某一路径时,Docker会在容器启动前,自动将当前容器镜像中对应挂载路径下的所有文件、目录结构、权限属性完整复制到这个空卷中,再完成挂载操作。
- 上述复制逻辑仅会在卷为空、首次挂载时触发一次。只要卷内已经存在内容(无论内容是初始化复制得到的,还是后续容器运行写入的),后续再将该卷挂载到任意容器的任意路径,都不会再触发复制,也不会用镜像内的文件覆盖卷内已有数据。
对应你的测试场景,执行流程是:
docker-compose build阶段仅生成包含/data/timestamp文件的my-image镜像,全程没有操作卷,符合预期。- 执行
docker-compose up时,Docker首先创建全新的空命名卷my-volume,再并行创建两个容器。 - 空卷首次挂载到my-service容器的
/data路径时,触发初始化复制逻辑,把my-image镜像里/data下的timestamp文件复制到my-volume中。 - 随后other-service容器挂载同一个my-volume到自身
/data路径时,卷内已经存在timestamp文件,不会再触发复制,因此直接使用原生alpine镜像的other-service也能读到这个时间戳文件,和你观察到的输出完全一致。
VOLUME指令的实际预期行为
Dockerfile中的VOLUME指令不会在构建阶段创建或挂载任何真实卷,它的实际作用只有两点:
- 仅在镜像元数据中添加标记,指定若干默认挂载路径。后续使用该镜像启动容器时,如果用户没有手动给这些标记路径指定挂载的卷或绑定目录,Docker会自动为这些路径创建并挂载匿名卷。
- 被VOLUME标记的路径,和运行时手动指定卷挂载的路径遵循完全一致的卷初始化规则:即首次挂载空卷时自动复制镜像对应路径的内容到卷中,不会因为是VOLUME指令标记的路径就产生特殊行为。这也是你把
RUN mkdir /data替换为VOLUME /data后现象没有变化的原因——两种写法下,容器启动时挂载空卷的初始化复制逻辑完全相同。
你可以通过一个简单测试验证这个逻辑:先删除现有my-volume卷,调整服务启动顺序让使用原生alpine镜像的other-service先挂载空的my-volume,再启动my-service,就会发现两个容器都读不到timestamp文件。原因是首次挂载空卷的是other-service,它的镜像中/data路径没有任何文件,初始化复制到卷里的就是空目录,后续my-service挂载时卷已经有内容(空目录),不会再复制镜像里的timestamp文件。
内容的提问来源于stack exchange,提问作者bimlas
相关产品推荐
相关产品推荐

