Dockerfile是Docker用来构建镜像的文本文件,包含自定义的指令和格局。能够通过docker build命令从Dockerfile中构建镜像。用户能够通过对立的语法命令来依据需要进行配置,通过这份对立的配置文件,在不同的文件上进行散发,须要应用时就能够依据配置文件进行自动化构建,这解决了开发人员构建镜像的简单过程。

Dockerfile的应用

Dockerfile形容了组装对象的步骤,其中每条指令都是独自运行的。除了FROM指令,其余每条命令都会在上一条指令所生成镜像的根底上执行,执行完后会生成一个新的镜像层,新的镜像层笼罩在原来的镜像之上从而造成了新的镜像。Dockerfile所生成的最终镜像就是在根底镜像下面叠加一层层的镜像层组建的。

Dockerfile指令

Dockerfile的根本格局如下:

# CommentINSTRUCTION arguments

在Dockerfile中,指令(INSTRUCTION)不辨别大小写,然而为了与参数辨别,举荐大写。
Docker会程序执行Dockerfile中的指令,第一条指令必须是FROM指令,它用于指定构建镜像的根底镜像。在Dockerfile中以#结尾的行是正文,而在其余地位呈现的#会被当成参数。

Dockerfile中的指令有FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOING、VOLUME、USER、WORKDIR、ONBUILD,谬误的指令会被疏忽。上面将具体解说一些重要的Docker指令。

FROM

格局: FROM <image> 或者 FROM <image>:<tag>

FROM指令的性能是为前面的指令提供根底镜像,因而Dockerfile必须以FROM指令作为第一条非正文指令。从公共镜像库中拉取镜像很容易,根底镜像能够抉择任何无效的镜像。
在一个Dockerfile中FROM指令能够呈现屡次,这样会构建多个镜像。tag的默认值是latest,如果参数image或者tag指定的镜像不存在,则返回谬误。

ENV

格局: ENV <key> <value> 或者 ENV <key>=<value> ...

ENV指令能够为镜像创立进去的容器申明环境变量。并且在Dockerfile中,ENV指令申明的环境变量会被前面的特定指令(即ENV、ADD、COPY、WORKDIR、EXPOSE、VOLUME、USER)解释应用。

其余指令应用环境变量时,应用格局为$variable_name或者${variable_name}。如果在变量背后增加斜杠\能够本义。如\\$foo或者\\${foo}将会被转换为$foo${foo},而不是环境变量所保留的值。另外,ONBUILD指令不反对环境替换。

COPY

格局: COPY <src> <dest>

COPY指令复制<src>所指向的文件或目录,将它增加到新镜像中,复制的文件或目录在镜像中的门路是<dest><src>所指定的源能够有多个,但必须是上下文根目录中的相对路径。
不能只用形如 COPY ../something /something这样的指令。此外,<src>能够应用通配符指向所有匹配通配符的文件或目录,例如,COPY home* /mydir/ 示意增加所有以"hom"结尾的文件到目录/mydir/中。

<dest>能够是文件或目录,但必须是指标镜像中的绝对路径或者绝对于WORKDIR的相对路径(WORKDIR即Dockerfile中WORKDIR指令指定的门路,用来为其余指令设置工作目录)。
<dest>以反斜杠/结尾则其指向的是目录;否则指向文件。<src>同理。若<dest>是一个文件,则<src>的内容会被写到<dest>中;否则<src>指向的文件或目录中的内容会被复制增加到<dest>目录中。
<src>指定多个源时,<dest>必须是目录。如果<dest>不存在,则门路中不存在的目录会被创立。

ADD

格局:ADD <src> <dest>

ADD与COPY指令在性能上很类似,都反对复制本地文件到镜像的性能,但ADD指令还反对其余性能。<src>能够是指向网络文件的URL,此时若<dest>指向一个目录,则URL必须是齐全门路,这样能够取得网络文件的文件名filename,该文件会被复制增加到<dest>/<filename>
比方 ADD http://example.com/config.pro... / 会创立文件/config.property。

<src>还能够指向一个本地压缩归档文件,该文件会在复制到容器时会被解压提取,如ADD sxample.tar.xz /。然而若URL中的文件为归档文件则不会被解压提取。

ADD 和 COPY指令尽管性能类似,但个别举荐应用COPY,因为COPY只反对本地文件,相比ADD而言,它更加通明。

EXPOSE

格局: EXPOSE <port> [<port>/<protocol>...]

EXPOSE指令告诉Docker该容器在运行时侦听指定的网络端口。能够指定端口是侦听TCP还是UDP,如果未指定协定,则默认值为TCP。
这个指令仅仅是申明容器打算应用什么端口而已,并不会主动在宿主机进行端口映射,能够在运行的时候通过docker -p指定。

EXPOSE 80/tcpEXPOSE 80/udp

USER

格局: USER <user>[:<group] 或者 USER <UID>[:<GID>]

USER指令设置了user name和user group(可选)。在它之后的RUN,CMD以及ENTRYPOINT指令都会以设置的user来执行。

WORKDIR

格局: WORKDIR /path/to/workdir

WORKDIR指令设置工作目录,它之后的RUN、CMD、ENTRYPOINT、COPY以及ADD指令都会在这个工作目录下运行。如果这个工作目录不存在,则会主动创立一个。
WORKDIR指令可在Dockerfile中屡次应用。如果提供了相对路径,则它将绝对于上一个WORKDIR指令的门路。例如

WORKDIR /aWORKDIR bWORKDIR cRUN pwd

输入后果是 /a/b/c

RUN

格局1: RUN <command> (shell格局)
格局2: RUN ["executable", "param1", "param2"] (exec格局,举荐应用)

RUN指令会在前一条命令创立出的镜像的根底上创立一个容器,并在容器中运行命令,在命令完结运行后提交容器为新镜像,新镜像被Dockerfile中的下一条指令应用。

RUN指令的两种格局表示命令在容器中的两种运行形式。当应用shell格局时,命令通过/bin/sh -c运行。
当应用exec格局时,命令是间接运行的,容器不调用shell程序,即容器中没有shell程序。
exec格局中的参数会被当成JSON数组被Docker解析,故必须应用双引号而不能应用单引号。因为exec格局不会在shell中执行,所以环境变量的参数不会被替换。

比方执行RUN ["echo", "$HOME"]指令时,$HOME不会做变量替换。如果心愿运行shell程序,指令能够写成 RUN ["/bin/bash", "-c", "echo", "$HOME"]

CMD

CMD指令有3种格局。

格局1:CMD <command> (shell格局)
格局2:CMD ["executable", "param1", "param2"] (exec格局,举荐应用)
格局3:CMD ["param1", "param2"] (为ENTRYPOINT指令提供参数)

CMD指令提供容器运行时的默认值,这些默认值能够是一条指令,也能够是一些参数。一个Dockerfile中能够有多条CMD指令,但只有最初一条CMD指令无效。
CMD ["param1", "param2"]格局是在CMD指令和ENTRYPOINT指令配合时应用的,CMD指令中的参数会增加到ENTRYPOING指令中.应用shell和exec格局时,命令在容器中的运行形式与RUN指令雷同。

不同之处在于,RUN指令在构建镜像时执行命令,并生成新的镜像;CMD指令在构建镜像时并不执行任何命令,而是在容器启动时默认将CMD指令作为第一条执行的命令。如果用户在命令行界面运行docker run命令时指定了命令参数,则会笼罩CMD指令中的命令。

ENTRYPOINT

ENTRYPOINT指令有两种格局。

格局1:ENTRYPOINT <command> (shell格局)
格局2:ENTRYPOINT ["executable", "param1", "param2"] (exec格局,举荐格局)

ENTRYPOINT指令和CMD指令相似,都能够让容器在每次启动时执行雷同的命令,但它们之间又有不同。一个Dockerfile中能够有多条ENTRYPOINT指令,但只有最初一条ENTRYPOINT指令无效。

当应用Shell格局时,ENTRYPOINT指令会疏忽任何CMD指令和docker run命令的参数,并且会运行在bin/sh -c中。这意味着ENTRYPOINT指令过程为bin/sh -c的子过程,过程在容器中的PID将不是1,且不能承受Unix信号。即当应用docker stop <container>命令时,命令过程接管不到SIGTERM信号。

举荐应用exec格局,应用此格局时,docker run传入的命令参数会笼罩CMD指令的内容并且附加到ENTRYPOINT指令的参数中。从ENTRYPOINT的应用中能够看出,CMD能够是参数,也能够是指令,而ENTRYPOINT只能是命令;另外,docker run命令提供的运行命令参数能够笼罩CMD,但不能笼罩ENTRYPOINT。

Dockerfile实际心得

应用标签

给镜像打上标签,有利于帮忙理解进镜像性能

审慎抉择根底镜像

抉择根底镜像时,尽量抉择以后官网镜像库的肩宽,不同镜像的大小不同,目前Linux镜像大小由如下关系:

busybox < debian < centos < ubuntu

同时在构建本人的Docker镜像时,只装置和更新必须应用的包。此外相比Ubuntu镜像,更举荐应用Debian镜像,因为它十分轻量级(目前其大小是在100MB以下),并且依然是一个残缺的公布版本。

充分利用缓存

Docker daemon会程序执行Dockerfile中的指令,而且一旦缓存生效,后续命令将不能应用缓存。为了无效地利用缓存,须要保障指令的连续性,尽量将所有Dockerfile文件雷同的局部都放在后面,而将不同的局部放到前面。

正确应用ADD与COPY命令

当在Dockerfile中的不同局部须要用到不同的文件时,不要一次性地将这些文件都增加到镜像中去,而是在须要时增加,这样也有利于反复利用docker缓存。
另外思考到镜像大小问题,应用ADD指令去获取近程URL中的压缩包不是举荐的做法。应该应用RUN wget或RUN curl代替。这样能够删除解压后不在须要的文件,并且不须要在镜像中在增加一层。

错误做法:

ADD http://example.com/big.tar.xz /usr/src/things/RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/thingsRUN make -C /usr/src/things all

正确的做法:

RUN mkdir -p /usr/src/things \    && curl -SL http://example.com/big.tar.xz \    | tar -xJC /usr/src/things \    && make -C /usr/src/things all

RUN指令

在应用较长的RUN指令时能够应用反斜杠\分隔多行。大部分应用RUN指令的常见是运行apt-wget命令,在该场景下请留神以下几点。

  1. 不要在一行中独自应用指令RUN apt-get update。当软件源更新后,这样做会引起缓存问题,导致RUN apt-get install指令运行失败。所以,RUN apt-get update和RUN apt-get install应该写在同一行。比方 RUN apt-get update && apt-get install -y package-1 package-2 package-3
  2. 防止应用指令RUN apt-get upgrade 和 RUN apt-get dist-upgrade。因为在一个无特权的容器中,一些必要的包会更新失败。如果须要更新一个包(如package-1),间接应用命令RUN apt-get install -y package-1。

CMD和ENTRYPOINT命令

CMD和ENTRYPOINT命令指定是了容器运行的默认命令,举荐二者联合应用。应用exec格局的ENTRYPOINT指令设置固定的默认命令和参数,而后应用CMD指令设置可变的参数。

比方上面这个例子:

FROM busyboxWORKDIR /appCOPY run.sh /appRUN chmod +x run.shENTRYPOINT ["/app/run.sh"]CMD ["param1"]

run.sh内容如下:

#!/bin/shecho "$@"

运行后输入后果为param1, Dockerfile中CMD和ENTRYPOINT的程序不重要(CMD写在ENTRYPOINT前后都能够)。

当在windows零碎下build dockerfile你可能会遇到这个问题

standard_init_linux.go:207: exec user process caused "no such file or directory"

这是因为sh文件的fileformat是dos,这里须要批改为unix,不须要下载额定的工具,个别咱们机器上装置了git会自带git bash,进入git bash,应用vi 编辑,在命令行模式下批改(:set ff=unix)。

不要再Dockerfile中做端口映射

应用Dockerfile的EXPOSE指令,尽管能够将容器端口映射在主机端口上,但会毁坏Docker的可移植性,且这样的镜像在一台主机上只能启动一个容器。所以端口映射应在docker run命令中用-p 参数指定。

# 不要再Dockerfile中做如下映射EXPOSE 80:8080# 仅裸露80端口,须要另做映射EXPOSE 80

实际Dockerfile的写法

Java 服务的DockerFile

FROM openjdk:8-jre-alpineENV spring_profiles_active=devENV env_java_debug_enabled=falseEXPOSE 8080WORKDIR /appADD target/smcp-web.jar /app/target/smcp-web.jarADD run.sh /appENTRYPOINT ./run.sh

能够看到根底镜像是openjdk,而后设置了两个环境变量,服务拜访端口是9090(意味着springboot利用中指定了server.port=8080),设置了工作目录是/app。通过ENTRYPOINT设定了启动镜像时要启动的命令(./run.sh)。这个脚本中的内容如下:

#!/bin/sh# Set debug options if requiredif [ x"${env_java_debug_enabled}" != x ] && [ "${env_java_debug_enabled}" != "false" ]; then    java_debug_args="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"fi# ex: env_jvm_flags="-Xmx1200m -XX:MaxRAM=1500m" for productionjava $java_debug_args $env_jvm_flags -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -jar target/smcp-web.jar

如果咱们要指定jvm的一些参数,能够通过在环境变量中设置env_jvm_flags来指定。

Maven Dockerfile

maven的Dockerfile也写的很好,这里我发上来也给大家参考下

FROM openjdk:8-jdkARG MAVEN_VERSION=3.6.3ARG USER_HOME_DIR="/root"ARG SHA=c35a1803a6e70a126e80b2b3ae33eed961f83ed74d18fcd16909b2d44d7dada3203f1ffe726c17ef8dcca2dcaa9fca676987befeadc9b9f759967a8cb77181c0ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binariesRUN mkdir -p /usr/share/maven /usr/share/maven/ref \  && curl -fsSL -o /tmp/apache-maven.tar.gz ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \  && echo "${SHA}  /tmp/apache-maven.tar.gz" | sha512sum -c - \  && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \  && rm -f /tmp/apache-maven.tar.gz \  && ln -s /usr/share/maven/bin/mvn /usr/bin/mvnENV MAVEN_HOME /usr/share/mavenENV MAVEN_CONFIG "$USER_HOME_DIR/.m2"COPY mvn-entrypoint.sh /usr/local/bin/mvn-entrypoint.shCOPY settings-docker.xml /usr/share/maven/ref/ENTRYPOINT ["/usr/local/bin/mvn-entrypoint.sh"]CMD ["mvn"]

能够看到它是基于openjdk这个根底镜像来创立的,先去下载maven的包,而后进行了装置。 而后又设置了MAVEN_HOME和MAVEN_CONFIG这两个环境变量,最初通过mvn-entrypoing.sh来进行了启动。

前端服务的两阶段构建

我有一个前端服务,目录构造如下:

$ ls frontend/myaccount/  resources/  third_party/

myaccount目录下是搁置的js,vue等,resources搁置的是css,images等。third_party放的是第三方利用。

这里采纳了两阶段构建,即采纳上一阶段的构建后果作为下一阶段的构建数据

FROM node:alpine as builderWORKDIR '/build'COPY myaccount ./myaccountCOPY resources ./resourcesCOPY third_party ./third_partyWORKDIR '/build/myaccount'RUN npm installRUN npm rebuild node-sassRUN npm run buildRUN ls /build/myaccount/distFROM nginxEXPOSE 80COPY --from=builder /build/myaccount/dist /usr/share/nginx/html

须要留神结尾的 --from=builder这里和结尾是一唱一和的。

总结

我置信看完dockerfile指令,你看任何一个dockerfile应该都没有太大问题,不记得的命令回来翻一下就行了。如果你感觉还能够,关注下哟。