Docker Compose Build与Docker Build多阶段构建缓存不共享问题
最近碰到个棘手的问题:先用docker-compose build构建多阶段Docker镜像后,再执行docker build .命令时,Docker的层缓存居然没法正常传递了。从构建日志能看出来,WORKDIR指令还能复用之前的缓存,但COPY指令明明文件一点没改,却直接跳过缓存生成了新的哈希值。单独用docker build或者单独用docker-compose build的时候都没这个问题,就两者先后使用的时候出状况。
构建日志
T02429:app$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE node 10 9b9d314e0a86 7 hours ago 904MB T02429:app$ docker-compose build test Building test Step 1/6 : FROM node:10 as base ---> 9b9d314e0a86 Step 2/6 : WORKDIR /usr/src/app ---> Running in fc2d962f4fd0 Removing intermediate container fc2d962f4fd0 ---> 362c0b907170 Step 3/6 : COPY package.json ./ ---> 7457757e0221 Step 4/6 : COPY yarn.lock ./ ---> 8bb824d2d7cd Step 5/6 : COPY jest.config.js ./ ---> 584c78a1cce8 Step 6/6 : COPY src src ---> 8d7aac67a142 Successfully built 8d7aac67a142 Successfully tagged test_image:latest T02429:app$ docker build -t full_build . Sending build context to Docker daemon 255kB Step 1/12 : FROM node:10 as base ---> 9b9d314e0a86 Step 2/12 : WORKDIR /usr/src/app ---> Using cache ---> 362c0b907170 Step 3/12 : COPY package.json ./ ---> 48e89caded31 Step 4/12 : COPY yarn.lock ./ ---> 58c942524315 Step 5/12 : COPY jest.config.js ./ ---> 02b7f45d1d90 Step 6/12 : COPY src src ---> 708fe2761b39 Step 7/12 : FROM node:10 as prod ---> 9b9d314e0a86 Step 8/12 : WORKDIR /usr/src/app ---> Using cache ---> 362c0b907170 Step 9/12 : COPY --from=base /usr/src/app/package.json package.json ---> 542eacbc1285 Step 10/12 : COPY --from=base /usr/src/app/src src ---> c40cbe92f9ec Step 11/12 : EXPOSE 3000 ---> Running in 66473d8427c7 Removing intermediate container 66473d8427c7 ---> f5b1b1deafab Step 12/12 : CMD [ "echo", "hello world" ] ---> Running in a8c4609e7159 Removing intermediate container a8c4609e7159 ---> 863470696544 Successfully built 863470696544 Successfully tagged full_build:latest
Dockerfile内容
# The instructions for the first stage FROM node:10 as base WORKDIR /usr/src/app COPY package.json ./ COPY yarn.lock ./ COPY jest.config.js ./ COPY src src # The instructions for second stage FROM node:10 as prod WORKDIR /usr/src/app COPY --from=base /usr/src/app/package.json package.json COPY --from=base /usr/src/app/src src EXPOSE 3000 CMD [ "echo", "hello world" ]
docker-compose.yml内容
version: '3.7' services: web: image: web_image:latest build: context: . target: prod test: image: test_image:latest build: context: . target: base environment: - CI=true command: echo Hello
问题原因与解决办法
其实这是因为docker-compose和docker build在处理多阶段构建的缓存时,对阶段命名的引用逻辑存在细微差异。
当你用docker-compose build --target=base构建base阶段后,生成的test_image:latest镜像虽然包含base阶段的内容,但docker build在执行时,会重新创建属于自己构建链的base阶段,而不是直接复用docker-compose生成的那个base镜像的缓存。Docker的缓存判断是基于构建历史、上下文哈希等元数据的,docker-compose和docker build在处理构建上下文时的细节不同,导致后续docker build认为COPY的文件上下文发生了变化,因此跳过缓存生成新的哈希。
给你两个可行的解决办法:
方法一:统一构建入口
要么全程用docker build构建所有阶段,要么全程用docker-compose build,避免交叉构建导致的缓存断裂。比如先执行docker build --target=base -t test_image:latest .,后续docker-compose build和docker build都能正常复用这个缓存。方法二:指定缓存来源
在docker-compose.yml的build配置中添加cache_from参数,明确让docker-compose复用指定的镜像缓存,或者反过来让docker build复用docker-compose生成的镜像。比如修改test服务的build部分:test: image: test_image:latest build: context: . target: base cache_from: - test_image:latest这样
docker-compose构建时会优先复用已有的test_image:latest缓存,后续docker build也能基于这个统一的缓存链进行复用。
你可以自己验证一下:用docker inspect test_image:latest查看docker-compose生成的镜像构建历史,再对比docker build生成的base阶段镜像的历史,会发现两者的构建元数据不一致,这就是Docker不认为它们是同一缓存层的核心原因。
内容的提问来源于stack exchange,提问作者Stralos

