格式:ONBUILD <其它指令>
。
ONBUILD
是一个特殊的指令,它后面跟的是其它指令,比如 RUN
, COPY
等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile
中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD
是为了帮助别人定制自己而准备的。
假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 npm
进行包管理,所有依赖、配置、启动信息等会放到 package.json
文件里。在拿到程序代码后,需要先进行 npm install
才可以获得所有需要的依赖。然后就可以通过 npm start
来启动应用。因此,一般来说会这样写 Dockerfile
:
FROM node:slim
RUN mkdir /app
WORKDIR /app
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
CMD [ "npm", "start" ]
把这个 Dockerfile
放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。但是如果我们还有第二个 Node.js 项目也差不多呢?好吧,那就再把这个 Dockerfile
复制到第二个项目里。那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题。
如果第一个 Node.js 项目在开发过程中,发现这个 Dockerfile
里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 Dockerfile
,再次构建,问题解决。第一个项目没问题了,但是第二个项目呢?虽然最初 Dockerfile
是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 Dockerfile
,而第二个项目的 Dockerfile
就会被自动修复。
那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 Dockerfile
的变化,重新构建后就继承了基础镜像的更新?好吧,可以,让我们看看这样的结果。那么上面的这个 Dockerfile
就会变为:
FROM node:slim
RUN mkdir /app
WORKDIR /app
CMD [ "npm", "start" ]
这里我们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 my-node
的话,各个项目内的自己的 Dockerfile
就变为:
FROM my-node
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
基础镜像变化后,各个项目都用这个 Dockerfile
重新构建镜像,会继承基础镜像的更新。
那么,问题解决了么?没有。准确说,只解决了一半。如果这个 Dockerfile
里面有些东西需要调整呢?比如 npm install
都需要加一些参数,那怎么办?这一行 RUN
是不可能放入基础镜像的,因为涉及到了当前项目的 ./package.json
,难道又要一个个修改么?所以说,这样制作基础镜像,只解决了原来的 Dockerfile
的前4条指令的变化问题,而后面三条指令的变化则完全没办法处理。
ONBUILD
可以解决这个问题。让我们用 ONBUILD
重新写一下基础镜像的 Dockerfile
:
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]
这次我们回到原始的 Dockerfile
,但是这次将项目相关的指令加上 ONBUILD
,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的 Dockerfile
就变成了简单地:
FROM my-node
是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile
构建镜像时,之前基础镜像的那三行 ONBUILD
就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install
,生成应用镜像。