Dockerfile 由一行行命令语句组成,并且支持以 # 开头的注释行。

AUFS不能超过 127 层

通常,Dockerfile 分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。
在这里插入图片描述

一、构建材料

在这里插入图片描述

run.sh

#!/bin/bash

echo "Hello Docker"
tail -f /dev/null

二、完整示例

FROM docker.io/nginx
LABEL author="运维@小兵" version="1.0" describe="构建nginx镜像"
ENV USERNAME nginx
USER ${USERNAME}
EXPOSE 80 443
WORKDIR /opt
VOLUME /data
ADD jdk-8u191-linux-x64.tar.gz /opt
COPY run.sh /tmp
RUN echo "${USERNAME}" >> /tmp/test.txt
CMD /tmp/run.sh

三、指令详解

FROM[必选]

用于指定构建镜像时依赖的基础镜像
格式:

FROM <image> 或 FROM<image>:<tag>	#不填tag默认为latest

例如:FROM docker.io/nginx

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

FROM scratch

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。

MAINTAINER[可选]

设置镜像作者相关信息,如作者名字,日期,邮件,联系方式等
MAINTAINER

示例:MAINTAINER 运维@小兵 邮箱地址

LABEL[可选]

指定该dockerfile的基本信息,如维护者、版本等

例如:LABEL author=“运维@小兵” version=“1.0” describe=“构建nginx镜像”

EXPOSE[可选]

例如:EXPOSE 80 443

告诉 Docker 服务,容器需要暴露的端口号

可通过docker history 镜像名查看暴露的端口号,在启动容器时通过 -p 参数让 Docker 主机分配一个端口转发到容器的指定端口
在这里插入图片描述

ENV[可选]

格式:

#一次设置一个变量
 ENV <key> <value>    
 #设置多个环境变量
 ENV <key1>=<value1> 	<key2>=<value2> \
 	 <key3>=<value3>   

指定一个环境变量,会被后续 RUN 指令使用,在容器中通过export可以查看

例如:ENV USERNAME nginx

验证

通过RUN命令把USERNAME的值保存到/tmp/test.txt

RUN echo "${USERNAME}" >> /tmp/test.txt

进入容器查看/tmp/test.txt的内容
在这里插入图片描述
使用export查看USERNAME的值
在这里插入图片描述

ADD[可选]

复制文件到镜像(ADD与COPY的区别在于,ADD会自动解压tar、zip、tgz、xz等归档文件,而COPY不会,同时ADD指令还可以接一个url下载文件地址)

一般建议使用COPY复制文件即可,用ADD会解压,导致镜像变大

例如:ADD jdk-8u191-linux-x64.tar.gz /opt
在这里插入图片描述
注意:ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

COPY[可选]

与ADD类似,对于压缩文件,不会自动解压
run.sh脚本需提前准备好,放在上下文路径下,通常为当前路径
例如:COPY run.sh /home/

注意:不能直接拷贝目录,但可以拷贝目录下的内容

COPY config/ /opt/config/	把当前config目录下所有文件拷贝到/opt/config/下,如果/opt/config不存在,会创建

还需要注意一点:使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。

VOLUME[可选]

容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。

为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

例如:VOLUME /data
这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。
在容器中会自动创建/data目录
在这里插入图片描述
使用docker inspect 容器名可以看到/data目录挂载到了本地机器上的 /var/lib/docker/volumes/***中
在这里插入图片描述

当然,运行时可以覆盖这个挂载设置。比如:

docker run -d -v mydata:/data 镜像名

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

VOLUME /var/data /var/log

指定容器中的/var/log挂载到宿主机的/var/data目录,等同于-v /var/data:/var/log

USER[可选]

指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户

例如:USER nginx

会创建nginx组及用户,进入到容器后是在nginx用户下,而不是root用户
在这里插入图片描述

WORKDIR[可选]

为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录,如果目录不在,则会自动创建目录

例如:WORKDIR /opt

进入到容器后是在/opt目录下,默认是在/下
在这里插入图片描述

ARG[可选]

设置构建镜像要传递的参数
ARG [=]

例如:ARG name=sss

RUN[可选,但用的频率非常高]

每条 RUN 指令将在当前镜像的基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行,多条命令用&&来连接,避免执行多个RUN,使得镜像层数变多。最好只留一个RUN
在这里插入图片描述

CMD

指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条 CMD 命令,只有

最后一条会被执行。如果用户在启动容器时指定了要运行的命令,则会覆盖掉 CMD 指定的命令。

SHELL模式

实际的命令会被包装为 sh -c 的参数的形式进行执行

CMD bash /home/run.sh

上述会变为

CMD [ "sh", "-c", "bash /home/run.sh" ]

EXEC模式(推荐)

这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。

CMD ["bash", "/home/run.sh"]

为什么推荐使用EXEC模式

因为使用 shell 模式之后,程序会以 /bin/sh -c 的子命令启动,并且 shell 格式下不会传递任何信号给程序。这也就导致,在 docker stop 容器的时候,运行的程序捕捉不到发送的信号,从而导致不能优雅的关闭容器。
参考文章:如何优雅的关闭容器?

ENTRYPOINT

配置容器启动后执行的命令

每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个 ENTRYPOINT 时,只有最后一个生效。

当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:

ENTRYPOINT ["executable", "param1", "param2"]

例如:ENTRYPOINT [“bash”, “/home/run.sh”]

ONBUILD[可选]

格式为:ONBUILD [INSTRUCTION]
配置当所创建的镜像作为其他新创建镜像的基础镜像时,所执行的操作指令。例如,Dockerfile 使用如下的内容创建了镜像 image-A。

…ONBUILD ADD . /app/srcONBUILD RUN /usr/local/bin/python-build –dir /app/src…

四、构建镜像

docker build -t nginx:1.0 .

-t	#指定镜像的名字和tag号
.	#代表安全上下文的相对路径

五、CMD与ENTRYPOINT在docker run时覆盖方法的不同处

CMD的覆盖方式

docker run -itd nginx:latest sleep 3600

ENTRYPOINT覆盖方式

docker run -itd --name a5 --entrypoint="" nginx:latest sleep 3600

六、注意事项

减少镜像层

  • 一次RUN指令形成新的一层,Shell命令都写在一个RUN指令里面,减少镜像层。
  • 文件比较多时,放入到一个目录中,使用COPY拷贝整个目录

避免使用ADD

优化镜像大小:清理无用数据

一次RUN形成新的一层,如果没有在同一层删除,无论文件是否最后删除,都会带到下一层,所以要在每一层清理对应的残留数据,减小镜像大小。

如执行完yum装包后需用yum clean all清除缓存包

多阶段进行镜像构建

例如,构建JAVA项目镜像:

# git clone https://github.com/lizhenliang/tomcat-java-demo
# cd tomcat-java-demo
# vi Dockerfile
FROM maven AS build
ADD ./pom.xml pom.xml
ADD ./src src/
RUN mvn clean package

FROM lizhenliang/tomcat
RUN rm -rf /usr/local/tomcat/webapps/ROOT
COPY --from=build target/*.war /usr/local/tomcat/webapps/ROOT.war
# docker build -t demo:v1 .
# docker container run -d -v demo:v1

首先,第一个FROM 后边多了个 AS 关键字,可以给这个阶段起个名字。
然后,第二部分FROM用的我们上面构建的Tomcat镜像,COPY关键字增加了—from参数,用于拷贝某个阶段的文件到当前阶段。这样一个Dockerfile就都搞定了。

COW写时复制技术决定不能在不同指令中操作同一目录

例如:

COPY files /opt/files
RUN chown -R test.test /opt/files

这会导致COPY层和RUN层均会复制一次/opt/files,如果/opt/files的大小为400M,那么RUN层的大小为400+M

建议:把类似chown -R test.test /opt/files的写操作放入init_container.sh脚本中,该脚本执行完毕后自己把自己删除

参考文章:

编写 Dockerfile 最佳实践
Dockerfile 定制专属镜像,超详细!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐