Dockerfile简介及书写规则

2023-05-16

本文章挺好,是转载!!!!看到私聊或者留言删除

一、Dockerfile定制镜像
Dockfile是一种被Docker程序解释的脚本,Dockerfile由一条一条的指令组成,每条指令对应Linux下面的一条命令,Docker程序将这些Dockerfile指令翻译真正的Linux命令;Dockerfile有自己书写格式和支持的命令,Docker程序解决这些命令间的依赖关系,类似于Makefile,Docker程序将读取Dockerfile,根据指令生成定制的image。

Dockerfile的指令是忽略大小写的,建议使用大写,使用#作为注释,每一行只支持一条指令,每条指令可以携带多个参数。

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层, 因此每一条指令的内容,就是描述该层应当如何构建。

二、Dockerfile 指令详解
1、FROM指定基础镜像
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个nginx镜像的容器,再进行修改一样,基础镜像是必须指定的。而FROM就是指定基础镜 像,因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。

在DockerStore上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如nginx 、redis、mongo 、mysql 、httpd 、php、 tomcat等;也有一些方便开发、构建、运行各种语言应用的镜像,如node、openjdk 、python、ruby 、golang等。可以 在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。

如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如ubuntu 、debian、centos、fedora 、alpine 等,这些操作系统的软件库为我们提供了 更广阔的扩展空间。

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为scratch。这个镜像 是虚拟的概念,并不实际存在,它表示一个空白的镜像。
如果你以scratch为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。

不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,比如swarm、coreos/etcd 。对于Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接FROMscratch 会让镜像体积更加小 巧。使用Go语言开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。

2、RUN执行命令
RUN指令是用来执行命令行命令的。由于命令行的强大能力,RUN指令在定制镜像时是最常用的指令之一。
格式如下两种:
1)shell格式:RUN<命令> ,就像直接在命令行中输入的命令一样。
RUN echo ‘Hello, Docker!’ >/usr/share/nginx/html/index.html

2)exec格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。
既然RUN就像Shell脚本一样可以执行命令,那么我们是否就可以像Shell脚本一样把每个命令对应一个RUN呢?比如这样:


Dockerfile中每一个指令都会建立一层,RUN也不例外。每一个RUN的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。
而上面的这种写法,创建了7层镜像。这是完全没有意义的,而且很多运行时不需要的东 西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常 多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。这是很多初学Docker的人常犯的一个错误。
UnionFS 是有最大层数限制的,比如AUFS,曾经是最大不得超过42层,现在是不得超过 127 层。
上面的Dockerfile正确的写法应该是这样:


首先,之前所有的命令只有一个目的,就是编译、安装redis可执行文件。因此没有必要建立 很多层,这只是一层的事情。因此,这里没有使用很多个RUN 对一一对应不同的命令,而是 仅仅使用一个RUN指令,并使用&&将各个所需命令串联起来。将之前的7层,简化为了1层。在撰写Dockerfile的时候,要经常提醒自己,这并不是在写Shell 脚本,而是在定义每 一层该如何构建。并且,这里为了格式化还进行了换行。Dockerfile支持Shell类的行尾添加“ \“的命令换行方式,以及行首 “#”进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障 更为容易,这是一个比较好的习惯。
此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的 软件,清理了所有下载、展开的文件,并且还清理了apt缓存文件。这是很重要的一步,我 们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清 理掉。
很多人初学Docker制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要 清理掉无关文件。

3、COPY复制文件
格式:
COPY:<源路径>… <目标路径>
COPY: [“<源路径1>”,… “<目标路径>”]
注:和RUN指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。
COPY指令将从构建上下文目录中<源路径>的文件或者目录复制到新的一层的镜像内的<目标路径>位置。
<目标路径>可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用WORKDIR指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
此外,还需要注意一点,使用COPY指令,源文件的各种元数据都会保留。比如读、写、执 行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用Git 进行管理的时候。

4、ADD更高级复制文件
ADD指令和COPY的格式和性质基本一致。但是在COPY 基础上增加了一些功能。
比如<源路径>可以是一个URL,这种情况下,Docker引擎会试图去下载这个链接的文件放到<目标路径>去。下载后的文件权限自动设置为600,如果这并不是想要的权限,那么还需 要增加额外的一层RUN进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样 还需要额外的一层RUN 指令进行解压缩。所以不如直接使用RUN指令,然后使用wget或 者curl工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并 不实用,而且不推荐使用。
如果<源路径>为一个tar压缩文件的话,压缩格式为gzip , bzip2以及xz的情况 下,ADD指令将会自动解压缩这个压缩文件到<目标路径>去。
在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像ubuntu 中:


但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用ADD命令了。
在Docker官方的 Dockerfile最佳实践文档中要求,尽可能的使用COPY,因为 COPY的语 义很明确,就是复制文件而已,而ADD则包含了更复杂的功能,其行为也不一定很清晰。最适合使用ADD的场合,就是所提及的需要自动解压缩的场合。另外需要注意的是,ADD指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
因此在COPY 和ADD指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用COPY指令,仅在需要自动解压缩的场合使用ADD 。

5、CMD容器启动命令
CMD指令的格式:
1)shell格式:CMD <命令>
2)exec 格式:CMD [“可执行文件”, “参数1”, “参数2”…] 参数列表格式:CMD [“参数1”, “参数2”…]。在指定了 ENTRYPOINT指令后,用CMD指定具体的参数。
Docker不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD指令就是用于指定默认的容器主进程的启动命令的。
在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu镜像默认的CMD 是/bin/bash ,如果我们直接docker run -it ubuntu 的话,会直接进入bash。我们也可以在运行时指定运行别的命令,如:docker run -it ubuntu cat/etc/os-release 。这就是用cat /etc/os-release命令替换了默认的/bin/bash命令了,输出了系统版本信息。
在指令格式上,一般推荐使用exec格式,这类格式在解析时会被解析为 JSON数组,因此 一定要使用双引号",而不要使用单引号。如果使用shell格式的话,实际的命令会被包装为sh -c的参数的形式进行执行。比如:


这就是为什么我们可以使用环境变量的原因,因为这些环境变量会被shell进行解析处理。提到CMD就不得不提容器中应用在前台执行和后台执行的问题。这是初学者常出现的一个混淆。
Docker不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用upstart/systemd去启动后台服务,容器内没有后台服务的概念。
一些初学者将CMD写为:
CMD service nginx start
然后发现容器执行后就立即退出了。甚至在容器内去使用systemctl 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在 以传统虚拟机的角度去理解容器。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
而使用service nginx start命令,则是希望 upstart 来以后台守护进程形式启动nginx服务。而刚才说了CMD service nginx start会被理解为CMD[ “sh”, “-c”, “service nginx start”] ,因此主进程实际上是sh 。那么当service nginx start命令结束后,sh也就结 束了,sh作为主进程退出了,自然就会令容器退出。 正确的做法是直接执行nginx可执行文件,并且要求以前台形式运行。比如:
CMD [ “nginx”, “-g”, “daemon off;” ]

6、ENTRYPOINT入口点
ENTRYPOINT的格式 RUN指令格式一样,分为exec 格式和shell格式。
ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比CMD要略显繁琐,需要通过docker run的参数 -entrypoint来指定。
当指定了ENTRYPOINT后,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给ENTRYPOINT指令,换句话说实际执行时,将变为:
<ENTRYPOINT> “<CMD>”
那么有人就会问有了CMD后,为什么还要有 ENTRYPOINT呢?这种<ENTRYPOINT>"<CMD>"有什么好处么?让我们来看几个场景。
场景一:让镜像变成像命令一样使用
假设我们需要一个得知自己当前公网IP的镜像,那么可以先用CMD来实现:


假如我们使用docker build -t myip来构建镜像的话,如果我们需要查询当前公网IP,只需要执行:

docker run myip
注:当前IP:61.148.226.66 来自:北京市联通

嗯,这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加 参数呢?比如从上面的CMD中可以看到实质的命令是curl,那么如果我们希望显示HTTP 头信息,就需要加上-i参数。那么我们可以直接加-i参数给docker run myip么?


我们可以看到可执行文件找不到的报错,executable file not found。之前我们说过,跟在镜像名后面的是command,运行时会替换CMD 的默认值。因此这里的-i替换了原来的CMD,而不是添加在原来的curl -s  http://ip.cn后面。而-i根本不是命令,所以自然找不到。
那么如果我们希望加入-i这参数,我们就必须重新完整的输入这个命令:
docker run myip curl -s  http://ip.cn -i
这显然不是很好的解决方案,而使用ENTRYPOINT就可以解决这个问题。现在我们重新用ENTRYPOINT来实现这个镜像:


这次我们再来尝试直接使用docker run myip -i:


可以看到,这次成功了。这是因为当存在ENTRYPOINT后,CMD的内容将会作为参数传给ENTRYPOINT,而这里-i就是新的CMD,因此会作为参数传给curl,从而达到了我们预期的效果。
场景二:应用运行前的准备工作
启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。比如mysql类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的mysql服务器运行之前解决。此外,可能希望避免使用root用户去启动服务,从而提高安全性,而在启动服务前还需要以root身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用root身份执行,方便调试等。 这些准备工作是和容器CMD无关的,无论CMD为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入ENTRYPOINT中去执行,而这个脚本会将接到的参数(也就是<CMD> )作为命令,在脚本最后执行。比如官方镜像redis中就是这么做的:


可以看到其中为了redis服务创建了 redis用户,并在最后指定了ENTRYPOINT为dockerentrypoint.sh脚本。


该脚本的内容就是根据CMD的内容来判断,如果是redis-server的话,则切换到redis用户身份启动服务器,否则依旧使用root身份执行。比如:


7、ENV 设置环境变量
ENV环境变量格式:
1) ENV <key> <value>
2) ENV <key1>=<value1> <key2>=<value2>…
注:这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。


这个例子中演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和Shell 下的行为是一致的。
定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方node 镜像Dockerfile中,就有类似这样的代码:


在这里先定义了环境变量NODE_VERSION,其后的RUN这层里,多次使用$NODE_VERSION来进行操作定制。可以看到,将来升级镜像构建版本的时候,只需要更新7.2.0即可, Dockerfile构建维护变得更轻松了。
下列指令可以支持环境变量展开:

ADD、COPY 、ENV、EXPOSE 、LABEL 、USER 、WORKDIR 、VOLUME 、STOPSIGNAL、ONBU ILD
注:可以从这个指令列表里感觉到,环境变量可以使用的地方很多,很强大。通过环境变量,我 们可以让一份Dockerfile制作更多的镜像,只需使用不同的环境变量即可。

8、ARG构建参数
格式:ARG<参数名>[=<默认值>]
构建参数和ENV的效果一样,都是设置环境变量。所不同的是,ARG所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用ARG保存密码之类的信息,因为docker history还是可以看到所有值的。

Dockerfile中的ARG指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令docker build中用--build-arg<参数名>=<值>来覆盖。
在1.13之前的版本,要求–build-arg中的参数名,必须在Dockerfile中用ARG 定义过 了,换句话说,就是–build-arg指定的参数,必须在Dockerfile中使用了。如果对应参 数没有被使用,则会报错退出构建。从1.13开始,这种严格的限制被放开,不再报错退出, 而是显示警告信息,并继续构建。这对于使用CI 系统,用同样的构建流程构建不同的Dockerfile的时候比较有帮助,避免构建命令必须根据每个Dockerfile的内容修改。

9、VOLUME定义匿名卷
格式为:
VOLUME [“<路径1>”, “<路径2>”…] VOLUME <路径>之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存 动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

VOLUME /data

这里的/data目录就会在运行时自动挂载为匿名卷,任何向 /data中写入的信息都不会记 录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设 置。比如:

docker run -d -v mydata:/data xxxx

在这行命令中,就使用了mydata这个命名卷挂载到了/data这个位置,替代了Dockerfile中定义的匿名卷的挂载配置。

10、EXPOSE声明端口
格式为EXPOSE <端口1> [<端口2>…]
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在Dockerfile中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是docker run -P时,会自动随机映射EXPOSE的端口。

此外,在早期Docker版本中还有一个特殊的用处。以前所有容器都运行于默认桥接网络中,因此所有容器互相之间都可以直接访问,这样存在一定的安全性问题。于是有了一个Docker 引擎参数–icc=false,当指定该参数后,容器间将默认无法互访,除非互相间使用了-links 参数的容器才可以互通,并且只有镜像中EXPOSE所声明的端口才可以被访问。这个–icc=false的用法,在引入了docker network 后已经基本不用了,通过自定义网络可以很 轻松的实现容器间的互联与隔离。

要将EXPOSE和在运行时使用-p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和 容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而EXPOSE仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。

11、WORKDIR指定工作目录
格式为WORKDIR <工作目录路径> 。
注:使用WORKDIR指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。
之前提到一些初学者常犯的错误是把Dockerfile等同于Shell脚本来书写,这种错误的理解还可能会导致出现下面这样的错误:


如果将这个Dockerfile进行构建镜像运行后,会发现找不到/app/world.txt文件,或者其 内容不是hello。原因其实很简单,在Shell中,连续两行是同一个进程执行环境,因此前 一个命令修改的内存状态,会直接影响后一个命令;而在Dockerfile中,这两行RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对Dockerfile构建分层存储的概念不了解所导致的错误。
之前说过每一个RUN都是启动一个容器、执行命令、然后提交存储层文件变更。第一层RUN cd /app的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。
因此如果需要改变以后各层的工作目录的位置,那么应该使用WORKDIR指令。

12、USER指定当前用户
格式:USER <用户名>
USER指令和 WORKDIR相似,都是改变环境状态并影响以后的层。WORKDIR是改变工作目录,USER则是改变之后层的执行RUN ,CMD以及ENTRYPOINT这类命令的身份。当然,和WORKDIR一样, USER只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。


如果以root执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来 运行某个服务进程,不要使用su或者sudo,这些都需要比较麻烦的配置,而且在TTY缺 失的环境下经常出错。建议使用gosu。


13、HEALTHCHECK 健康检查
格式:
1)HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
2)HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

HEALTHCHECK指令是告诉Docker 应该如何进行判断容器的状态是否正常,这是Docker1.12 引入的新指令。

在没有HEALTHCHECK指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在1.12以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。
而自1.12之后,Docker提供了HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。

当在一个镜像指定了HEALTHCHECK指令后,用其启动容器,初始状态会为starting ,在HEALTHCHECK指令检查成功后变为healthy,如果连续一定次数失败,则会变为unhealthy。

HEALTHCHECK支持下列选项:
–interval=<间隔>:两次健康检查的间隔,默认为30秒;
–timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被 视为失败,默认30 秒;
–retries=<次数>:当连续失败指定次数后,则将容器状态视为unhealthy,默认3 次。

和CMD ,ENTRYPOINT一样, HEALTHCHECK只可以出现一次,如果写了多个,只有最后一个生效。

在HEALTHCHECK [选项] CMD后面的命令,格式和ENTRYPOINT一样,分为shell格式,和exec格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1 :失败;2:保留,不要使用这个值。

假设我们有个镜像是个最简单的Web服务,我们希望增加健康检查来判断其 Web服务是否 在正常工作,我们可以用curl来帮助判断,其 Dockerfile的HEALTHCHECK可以这么写:


这里我们设置了每5秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过3秒没响应就视为失败,并且使用curl -fs  http://localhost/ || exit 1作为健康检查命令。
使用docker build来构建这个镜像:


构建好了后,我们启动一个容器:


当运行该镜像后,可以通过docker ps看到最初的状态为(health: starting):


在等待几秒钟后,再次docker ps,就会看到健康状态变化为了(healthy):


如果健康检查连续失败超过了重试次数,状态就会变为(unhealthy) 。
为了帮助排障,健康检查命令的输出(包括stdout以及stderr)都会被存储于健康状态里,可以用docker inspect 来查看。


14、ONBUILD为他人做嫁衣裳
格式:ONBUILD <其它指令> 。
ONBUILD是一个特殊的指令,它后面跟的是其它指令,比如RUN ,COPY等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile中的其它指令都是为了定制当前镜像而准备的,唯有ONBUILD是为了帮助别人定制自己而准备的。
假设我们要制作Node.js所写的应用的镜像。我们都知道Node.js使用npm 进行包管理,所有依赖、配置、启动信息等会放到package.json文件里。在拿到程序代码后,需要先进行npm install才可以获得所有需要的依赖。然后就可以通过npm start来启动应用。因此,一般来说会这样写Dockerfile:


把这个Dockerfile放到Node.js项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。但是如果我们还有第二个Node.js 项目也差不多呢?好吧,那就再把这Dockerfile复制到第二个项目里。那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题。
如果第一个Node.js项目在开发过程中,发现这个Dockerfile里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个Dockerfile,再次构建,问题解决。第一个项目没问题了,但是第二个项目呢?虽然最初Dockerfile是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的Dockerfile,而第二个项目的Dockerfile就会被自动修复。
那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更 新,各个项目不用同步Dockerfile的变化,重新构建后就继承了基础镜像的更新?好吧, 可以,让我们看看这样的结果。那么上面的这个Dockerfile就会变为:


这里我们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为mynode 的话,各个项目内的自己的Dockerfile就变为:


基础镜像变化后,各个项目都用这个Dockerfile重新构建镜像,会继承基础镜像的更新。
那么,问题解决了么?没有。准确说,只解决了一半。如果这个Dockerfile里面有些东西 需要调整呢?比如npm install都需要加一些参数,那怎么办?这一行RUN是不可能放入 基础镜像的,因为涉及到了当前项目的./package.json ,难道又要一个个修改么?所以说,这样制作基础镜像,只解决了原来的Dockerfile的前4条指令的变化问题,而后面三条指令的变化则完全没办法处理。
ONBUILD可以解决这个问题。让我们用 ONBUILD重新写一下基础镜像Dockerfile:


这次我们回到原始的Dockerfile,但是这次将项目相关的指令加上ONBUILD ,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的Dockerfile就变成了简单地:

FROM my-node

是的,只有这么一行。当在各个项目目录中,用这个只有一行的Dockerfile构建镜像时, 之前基础镜像的那三行ONBUILD就会开始执行,成功的将当前项目的代码复制进镜像、并且 针对本项目执行npm install,生成应用镜像。

参考文档
Dockerfie官方文档: https://docs.docker.com/engine/reference/builder/
Dockerfile最佳实践文档: https://docs.docker.com/engine/userguide/engimage/dockerfile_best-practices/
Docker官方镜像 Dockerfile: https://github.com/docker-library/docs

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Dockerfile简介及书写规则 的相关文章

  • 最优化算法——常见优化算法分类及总结

    之前做特征选择 xff0c 实现过基于群智能算法进行最优化的搜索 xff0c 看过一些群智能优化算法的论文 xff0c 在此做一下总结 最优化问题 在生活或者工作中存在各种各样的最优化问题 xff0c 比如每个企业和个人都要考虑的一个问题
  • 利用手机摄像头采集图片运行ORB-SLAM2

    一 ROS配置安装 二 ORB SLAM2配置安装 可参考前文 ROS仿真环境安装与配置 身在江湖的郭大侠的博客 CSDN博客 三 Android手机摄像头与ROS建立通信 GitHub有个开源的项目 xff0c 可以通过wifi将摄像头捕
  • VINS_FUSION

    VINS FUSIO xff2e 意义 VINS Fusion在VINS Mono的基础上 xff0c 添加了GPS等可以获取全局观测信息的传感器 xff0c 使得VINS可以利用全局信息消除累计误差 xff0c 进而减小闭环依赖 此外 x
  • VINS_FUSION编译运行

    一 ROS安装 见前文 二 ceres安装 GitHub地址 xff1a GitHub ceres solver ceres solver A large scale non linear optimization library 14 0
  • Turtlebot2简单控制

    遥控 遥控前为了让turtlebot接受命令 xff0c 需要启动 roslaunch turtlebot bringup minimal lauch 键盘操作命令 xff1a roslaunch turtlebot teleop keyb
  • turtlebot2利用turtlebot_exploration_3d进行自主建图

    安装octomap ros和rviz插件 sudo apt get install ros indigo octomap 源码安装 xff1a turtlebot exploration 3d 本机为Ubuntu16对应的ros版本为kin
  • CSDN周赛第十二期

    CSDN的比赛难度相对而且比较简单 xff0c 适合小白练手 xff0c 而且刷题本身在于锻炼思维 xff0c 写出题目不难 xff0c 但是需要拥有缜密的思维才能通过全部的用例 1 豚鼠排名榜 已知字符A B C 每个字符都有自己的权值q
  • rocon app

    规格 xff1a Rapp指代rocon app或者叫robot app 用在机器人的控制中Robotics in Concert xff0c 这是通过 Rapp Manager 安装和执行的元数据 它旨在允许更高级别的控制器采用提供所需公
  • 矩阵按键的两种扫描方法

    1 实验目的 掌握两种按键扫描方法 xff1a 行扫描 xff0c 行列扫描 xff08 高低电平翻转 xff09 2 实验流程图 3 代码分析 xff08 1 xff09 行扫描 include 34 stm32f10x h 34 u16
  • 计算机二进制的浮点表示法

    计算机二进制的浮点表示法 0 1 3 61 0 3 组成 符号位 0表示正 1表示负 指数位 2的n次方 xff0c n 43 127 对于4字节浮点数 xff0c 此处为127 再换算成二进制 尾数位 直接用 过程 将整数部分换算成二进制
  • STM32延时函数的三种方法——最好掌握第三种

    单片机编程过程中经常用到延时函数 xff0c 最常用的莫过于微秒级延时delay us 和毫秒级delay ms 1 普通延时法 这个比较简单 xff0c 让单片机做一些无关紧要的工作来打发时间 xff0c 经常用循环来实现 xff0c 不
  • 【常用模块】HC-05蓝牙串口通信模块使用详解(实例:手机蓝牙控制STM32单片机)

    HC 05蓝牙串口通信模块应该是使用最广泛的一种蓝牙模块之一了 为什么呢 xff1f 因为HC05模块是一款高性能主从一体蓝牙串口模块 xff0c 可以不用知道太多蓝牙相关知识就可以很好的上手 说白了 xff0c 只是个蓝牙转串口的设备 x
  • uln2003驱动电路

    一 uln2003有什么作用 ULN2003是大电流驱动阵列 xff0c 多用于单片机 智能仪表 PLC 数字量输出卡等控制电路中 可直接驱动继电器等负载 输入5VTTL电平 xff0c 输出可达500mA 50V ULN2003是高耐压
  • 简单超声波测距

    用到模块 hc sr04超声波模块 xff0c stm32开发板 本实验通过超声波测距模块得到长度 直接打印到窗口显示 xff0c 故主要用到定时器函数 xff0c 串口函数 hcsr04 c 只需要提供一个 10uS以上脉冲触发信号 xf
  • Javaer,你必须要了解的ExecutorService

    ExecutorService初接触 之前做的一个功能里有一个耗时操作 xff1a 处理数据库里对应的记录 xff0c 然后将每个处理后的结果做个排序 恕本人小白 xff0c 刚开始直接用单线程处理 xff01 你敢信 xff1f xff0
  • 平衡自行车-理论篇

    原文链接 xff1a http nicekwell net blog 20180121 ping heng zi xing che li lun pian html 一 模型分析 1 倒立摆2 自行车的平衡控制 2 1 怎样的状态才叫平衡2
  • 魔百盒CM201-1刷机教程

    家里有一块魔百盒CM201 1一直在家积灰 xff0c 由于看到网上教程可以刷各种系统 xff0c 所以想着玩来试试看 先刷一个电视版安卓系统看 盒子样子大概就是下面这样 xff1a 拿到手之后就迫不及待的将外壳拆掉了 xff0c 下面这样
  • RK3288刷机教程:安装Ubuntu 16.04

    网上有很多基于瑞芯微RK3288芯片的板子 xff0c 个人感觉配置都非常不错 xff01 然后就淘了两块玩玩 如下图所示 xff1a 然后可以看到 xff0c 各种接口也比较全乎 xff01 有HDMI和VGA视频输出接口 xff0c 两
  • ros串口通讯(读取串口数据)

    ros串口通讯是非常重要的通讯手段 xff0c 通常跟下位机或者各种usb口外设都是通过串口进行通讯的 那么我们跟着教程来学习一下如何读取手机通过无线串口发送给电脑的数据 这里我通过一个usb ttl工具将蓝牙连接到电脑上 xff0c 然后
  • No package ‘orocos-bfl‘ found

    目录 问题 xff1a 原因 xff1a 解决办法 xff1a 问题 xff1a 在编译ros工程的时候 xff0c 出现如下错误提示 xff1a No package 39 orocos bfl 39 found 如下图所示 xff1a

随机推荐

  • 人工智能(AI)入门

    人工智能的入门学习需要具备的知识结构 xff1a 一 编程语言选择 推荐python xff0c 原因有二 xff0c 其一 xff0c 语法简单易学 xff1b 其二 xff0c 有丰富的库支持 二 算法设计基础 人工智能的研究内容集中在
  • 卡尔曼滤波(Kalman filter)算法以及Arduino应用-mpu6050(导航贴)

    正在更新中 这篇文章要跟大家一起完全搞明白卡尔曼滤波 xff0c 连一个标点符号也不放过 xff0c 完完全全理解明白 如果你看不懂 xff0c 那说明我写的不好 本文是看了dr con博士的视频后做的 xff0c 建议可以去看看 如果哪里
  • ROS发布tf坐标

    我们写个小程序来发布一个坐标系 xff1a 坐标系消息格式 xff1a std msgs Header header 头信息 uint32 seq 序列号 time stamp 时间戳 string frame id 坐标 ID strin
  • pop_back()的用法及运行机制

    vector在c 43 43 中非常好用 xff0c 简单的说 xff0c vector是一个能够存放任意类型的动态数组 能够增加和压缩数据 一般使用push back 和pop back 函数将数据存放进容器末尾 如下例程 xff1a i
  • Gazebo启动不开

    问题 xff1a 按照书上的指引 xff0c 启动gazebo仿真软件 当然记得运行roscore rosrun gazebo ros gazebo 结果我在这个页面等了三分钟一点儿动静也没有 查阅资料 xff0c 说明这是因为model库
  • Gazebo仿真小例程一(通过例程熟悉整个仿真步骤)

    目录 1 编辑urdf文件 xff08 1 xff09 dynamic标签 xff08 2 xff09 gazebo标签 xff08 3 xff09 transmission标签 xff08 4 xff09 ros control插件 2
  • Arduino ide配置esp32硬件支持(配置esp32的arduino开发环境)

    ESP32学习导航帖 前言 当我们用arduino ide基于esp32开发板进行程序开发的时候 xff0c arduino ide按照默认安装之后是无法直接给esp32下载程序的 xff0c 也不支持esp32相关的库 这主要是默认的ar
  • AS5600磁编码器的使用以及简单的滤波算法(arduino)

    目录 前言 实践 示例一 xff1a 发现IIC设备 示例二 xff1a 读取AS5600原始数据 示例三 xff1a 对读取到的AS5600原始数据进行低通滤波 1 一阶滤波算法的原理 2 编程实现 前言 AS5600磁编码器常用于电机的
  • 平衡小车的控制算法(PID,LQR,MPC)及arduino程序导航贴

    目录 平衡小车电机位置测试小实验 1 编码器脉冲计数 PID控制算法 平衡小车PID调参实验 位置环 2 编码器计数转换角度 小车整体的动力学建模 通过特征值判断系统动态特性 龙伯格观测器 平衡小车电机位置测试小实验 1 编码器脉冲计数 c
  • the selected library block “Contact_forces_lib/3D/sphere to plane force“ no longer exists

    问题 在matlab的simulink里面进行simscape仿真的时候 xff0c 由于添加了接触力 xff0c 因此实现装了 Simscape Multibody Contact Forces Library 这个库 xff0c 装完之
  • matlab画圆(及其他常用图形)

    画图 1 matlab画圆 xff08 1 xff09 代码 xff1a x 61 y 61 r 61 1 for i 61 1 100 x i 61 r cos i 2 pi 100 y i 61 r sin i 2 pi 100 plo
  • Linux或Ubuntu中查看磁盘空间大小的10个df命令

    在Linux中 xff0c 您可以使用名为df命令的命令行工具检查磁盘空间 df命令代表磁盘文件系统 使用df命令 xff0c 您可以在Linux上找到磁盘空间摘要信息 xff0c 例如可用磁盘空间和已用磁盘空间 在本教程中 xff0c 我
  • 调试平衡小车过程中间遇到的问题

    目录 定时器函数执行周期跟定时时间不一致 xff1f drv8833这款驱动器可以制作平衡小车用吗 xff1f 电机编码器AB相无输出 xff1f 平衡小车的角度标定一定要准确 平衡小车前进后退的控制逻辑是什么 xff1f 定时器函数执行周
  • CircuitPython入门贴

    前言 以前一直玩arduino的 xff0c 但是恼火于每次写完代码 xff0c 都要编译并烧录到MCU上 xff0c 刚开始的blink例程类的小代码还好 xff0c 起码十几秒钟就能烧录成功 xff0c 但是后来随着程序代码量的增加 x
  • ROS+Arduino学习导航贴

    前言 原先写了一些ROS 43 arduino学习记录的帖子 xff0c 发现每次找起来非常麻烦 xff0c 所以做一个汇总帖 xff0c 以后需要的话 xff0c 找起来就方便了 关于我用的开发板 xff0c 一开始学习的时候 xff0c
  • Ready! Use 192.168.191.2 to access clientGuru Meditation Error: Core 1 panic‘ed (LoadProhibited).

    问题描述 在用esp32通过wifi和ros主机进行话题通信的过程中 xff0c 串口一直打印如下信息 xff1a Connecting to Zhitong Ready Use 192 168 191 2 to access client
  • Baize_ServoDriver_esp32(ROS+Arduino驱动舵机机械臂,通过串口或WiFi话题通信)(数字孪生:虚拟和现实同步)

    介绍 硬件平台 控制板 xff1a Baize ServoDriver esp32 舵机机械臂 xff1a 通过Baize ServoDriver esp32这块舵机驱动板 xff0c 我们来驱动我们的机器人 首先 xff0c 我们通过串口
  • FreeRTOS 学习五:信号量和互斥锁

    1 简介 xff1a 此篇文章中涉及的函数除特殊说明 xff0c 包含的头文件都是 semphr h二值信号量同linux中的原子量 xff0c 我们可以看成像是一个锁 xff0c 在使用的时候 xff0c 需要能拿到锁才能执行程序 xff
  • strchr, strrchr, strchrnull

    名字 strchr strrchr strchrnul 在字符串中定位一个字符 概要 include lt string h gt char strchr const char s int c char strrchr const char
  • Dockerfile简介及书写规则

    本文章挺好 xff0c 是转载 xff01 xff01 xff01 xff01 看到私聊或者留言删除 一 Dockerfile定制镜像 Dockfile是一种被Docker程序解释的脚本 xff0c Dockerfile由一条一条的指令组成