浅言碎语
众所周知,构建 docker 镜像只有有两种方式
docker commit
命令将正在运行的容器
,叠加上可写层的修改内容
,以此来生成一个新的镜像
- 语法格式:
docker commit <容器名称> <新镜像tag>
- 语法格式:
docker build
命令通过Dockerfile 文件
内的步骤一步步构建镜像
- 语法格式:
docker build -t <新镜像tag> .
- 语法格式:
Dockerfile 基本结构
Dockerfile 一般分为四个部分
- 基础镜像信息
- 维护者信息
- 镜像操作指令
- 容器启动时执行的命令
Dockerfile 指令
- 解析器指令
不区分大小写
,任何解析器指令后面必须包含一个空格,解析器指令不支持换行符 - Dockerfile 注释符号为
#
这里列出一些比较常用的指令,docker 官方文档还有更多的指令可以学习
FROM
指定基础镜像(必须指定,否则 Dockerfile 无效)
语法格式:
FROM <镜像名称>:<镜像tag> [AS <阶段名称>]
- 无论是
docker pull
、docker run
、还是Dockerfile
内,不表明镜像tag
,默认的镜像tag
都是latest
AS <阶段名称>
在多阶段构建,或者一些特殊原因,将多个镜像构建写在一个 Dockerfile 内的场景下使用
- 多个镜像分开构建,但是都写在一个 Dockerfile 的时候,
docker build
命令加上--target
参数指定阶段名称,就可以只构建指定阶段名称的镜像- 没有标注
AS <阶段名称>
的时候,Dockerfile 默认会以数字来标识阶段,第一个节点为数字0
,以此类推- 一个
FROM
表示一个阶段- Dockerfile 必须以
FROM
开头(只有ARG
指令可以在FROM
指令之前)
FROM centos
FROM centos:7
ARG
定义 Dockerfile 构建时的变量
语法格式:
ARG <变量名称>=<变量值>
只定义了变量,没有变量值的情况下,可以在
docker build
命令后面加上--build-arg <变量名称><变量值>
的方式,将变量值带入构建
- 如果 Dockerfile 内没有定义这个变量,使用
--build-arg <变量名称><变量值>
会返回[Warning] One or more build-args [<变量名称>] were not consumed.
ARG os_version
ARG os_version=7
ARG 指令生效范围
FROM
之前的ARG
对当前 Dockerfile 内的所有FROM
生效,但对于FROM
后的构建不生效一个用来验证的 Dockerfile 模板
ARG os_version=7
FROM centos:${os_version} AS arg_test1
RUN echo "${os_version}" > /tmp/arg_test.log
FROM centos:${os_version} AS arg_test2
CMD echo "This is a test."
证明 FROM 之前的 ARG 对 FROM 后的构建不生效
先构建
arg_test1
,
docker build -t arg_test:1 --target arg_test1 .
Downloaded newer image for centos:7
可以看出,FROM
之前的ARG
,针对FROM
是生效的,拉取的镜像的确是centos:7
eeb6ee3f44bd
是centos:7
镜像的id
Sending build context to Docker daemon 18.6MB
Step 1/3 : ARG os_version=7
Step 2/3 : FROM centos:${os_version} AS arg_test1
7: Pulling from library/centos
2d473b07cdd5: Already exists
Digest: sha256:9d4bcbbb213dfd745b58be38b13b996ebb5ac315fe75711bd618426a630e0987
Status: Downloaded newer image for centos:7
---> eeb6ee3f44bd
Step 3/3 : RUN echo "${os_version}" > /tmp/arg_test.log
---> Running in 74b009ce6878
Removing intermediate container 74b009ce6878
---> 59846e43cd35
Successfully built 59846e43cd35
Successfully tagged arg_test:1
进入容器查看文件内容,我们会发现文件是空的
docker run -it arg_test:1 bash
cat tmp/arg_test.log
证明 FROM 之前的 ARG 对当前 Dockerfile 内所有 FROM 都生效
还是沿用上面的 Dockerfile 文件
docker build -t arg_test:2 --target arg_test2 .
从第二层的 FROM 可以看出,没有再次去拉取镜像,eeb6ee3f44bd
使用的是这个 id 的镜像,咱们上面也可以看到,就是第一次构建的时候拉取的cetnos:7
镜像
Sending build context to Docker daemon 18.6MB
Step 1/5 : ARG os_version=7
Step 2/5 : FROM centos:${os_version} AS arg_test1
---> eeb6ee3f44bd
Step 3/5 : RUN echo "${os_version}" > /tmp/arg_test.log
---> Running in 09636e8db1d9
Removing intermediate container 09636e8db1d9
---> 8db76f18804d
Step 4/5 : FROM centos:${os_version} AS arg_test2
---> eeb6ee3f44bd
Step 5/5 : CMD sleep
---> Running in 9f4697b3300f
Removing intermediate container 9f4697b3300f
---> d7361aff5854
Successfully built d7361aff5854
Successfully tagged arg_test:2
如何让 FROM 之前的 ARG 在 FROM 后的构建生效
只需要在FROM
后面,使用ARG <变量名称>
来再次引用即可
ARG os_version=7
FROM centos:${os_version}
ARG os_version
RUN echo "${os_version}" > /tmp/arg_test.log
docker build -t arg_test:3 .
进入容器查看文件内容,我们会发现文件里面会有
ARG
定义的变量值
docker run -it arg_test:3 bash
cat tmp/arg_test.log
MAINTAINER(官方已弃用)
功能描述,官方建议使用LABEL
语法格式:
MAINTAINER <name>
MAINTAINER chen2ha
LABEL
功能描述,以键值对的形式,将元数据添加到镜像种语法格式:
LABEL <key>=<value> <key>=<value> <key>=<value> ......
多个标签以空格隔开
LABEL maintainer="chen2ha" today="2022-05" say="hello"
也可以使用这种方式
LABEL maintainer="chen2ha" \
today="2022-05" \
say="hello"
查看镜像
LABEL
信息
docker image inspect --format='' <镜像名称>
ENV
设置环境变量语法格式:
ENV <key> <value>
#<key>
之后的所有内容均会被视为其<value>
的组成部分,因此,一次只能设置一个变量
ENV <key>=<value>
ENV JAVA_HOEE=/usr/local/jdk1.8.0_231
ENV PATH=${PATH}:${JAVA_HOME}/bin
USER
指定容器运行的用户和组
语法格式:
USER <用户名>[:<组名>]
USER <uid>[:<gid>]
- 也可以只指定用户或者组
使用
USER
指定用户后,Dockerfile 中USER
之后的指令:RUN
、CMD
、ENTRYPOINT
都将使用该用户
- 镜像构建完成后,通过
docker run
运行容器时,可以通过-u
参数来覆盖所指定的用户
USER www
WORKDIR
指定工作目录
语法格式:
WORKDIR <绝对路径>
RUN
、CMD
、ENTRYPOINT
、ADD
、COPY
等指令都会在WORKDIR
指定的路径下执行
WORKDIR
指令可以在 Dockerfile 内多次使用,镜像构建时,最后一个WORKDIR
为进入容器时的默认路径WORKDIR
可以使用ARG
和ENV
变量
ARG deploy_path=/opt/
WORKDIR ${deploy_path}
RUN
在镜像当前层执行指定的命令
语法格式:
RUN <shell 命令>
# shell 的格式
RUN ["<可执行文件>","参数1","参数2"]
# exec 的格式
- exec 会被解析成 json 数组,必须使用
RUN ["<可执行文件>","参数1"]
的格式- exec 不会发生正常的 shell 处理,比如
RUN ["echo","$USER"]
,不会像 shell 一样将$USER
替换成变量值
FROM centos:7
RUN yum install -y \
gcc \
gcc-c++
写 RUN 的一些小细节
相似的操作两次 RUN 和一次 RUN 的区别
- 两次 RUN
FROM centos:7
RUN yum install -y vim
RUN yum install -y wget
构建完成后,使用docker images
命令查看镜像大小,我这边构建完成后的大小是591MB
docker build -t centos:2_run .
docker images | grep 2_run
- 一次 RUN
FROM centos:7
RUN yum install -y \
vim \
wget
构建完成后,使用docker images
命令查看镜像大小,我这边构建完成后的大小是425MB
docker build -t centos:1_run .
docker images | grep 1_run
- 使用 yum clean 清除缓存
FROM centos:7
RUN yum install -y \
vim \
wget && \
yum clean all
构建完成后,使用docker images
命令查看镜像大小,我这边构建完成后的大小是284MB
docker build -t centos:1_run_clean .
docker images | grep run_clean
- 可以看出,
都是 yum 操作
,写两次 RUN
和一次 RUN
,镜像大小可以相差174MB
,如果一个 RUN
还加上 yum clean all 清除 yum 缓存
,镜像大小可以相差307MB
COPY
复制文件到镜像内
语法格式:
COPY [--chown=<用户名>:<组名>] <源路径> <目标路径>
--chown
参数只适用于 Linux 镜像,不适用于 Windows 镜像
--chown
可以写用户名
或者uid
,组名
或者gid
COPY
指令在 Dockerfile 内支持通配符,使用 go filepath 规则COPY --from <阶段名称>
可以从多构建环境中的其他镜像内复制文件
COPY --from <镜像>
可以从任何镜像内复制文件
- 不管是那种方式,源路径文件必须要存在
COPY
指令源路径如果是宿主机
上的文件,源文件的一级目录
必须要和 Dockerfile 文件处于同级目录
COPY
指令的目标路径如果不是绝对路径
,那目标路径将会以WORKDIR
路径为相对路径
COPY xxx.sh /usr/local/src/
# 从其他镜像复制文件
COPY --from=quay.io/coreos/etcd:v3.3.9 /usr/local/bin/etcd /usr/local/bin/
从其他阶段复制文件(多阶段构建)
FROM centos:7 as build
# <此处省略构建过程>
FROM centos:7
COPY --from=build /build/build_server /build
ADD
复制文件到镜像内
语法格式:
ADD [--chown=<用户名>:<组名>] <源路径> <目标路径>
--chown
参数只适用于 Linux 镜像,不适用于 Windows 镜像
--chown
可以写用户名
或者uid
,组名
或者gid
ADD
指令在 Dockerfile 内支持通配符,使用 go filepath 规则
ADD
指令相比较COPY
指令,多了两个功能
如果源文件是
压缩包
类型的文件
- 需要解压,使用
ADD
指令会自动解压,无需使用RUN
指令执行解压操作- 不需要解压,使用
COPY
参数如果源文件是
网络
类型文件
直接使用
ADD
参数,可以从网络下载源文件,如果是压缩包类型,也会自动解压
ADD
指令不支持身份验证,网络源文件如果需要身份验证,还是需要使用RUN wget
或者RUN curl
- 不推荐使用
ADD
将网络源文件放到镜像内,会多增加一层解压操作,导致镜像会变大,推荐使用RUN wget xxx && tar xf xxx && rm -f xxx
可以减少层数以及镜像的大小
# 添加所有以"hom"开头的文件
ADD hom* /mydir/
# 添加 "test.tar.gz" 到 WORKDIR/relativeDir/
ADD test.tar.gz relativeDir/
EXPOSE
容器启动时监听的端口(默认 tcp 协议),可以指定为 udp 协议
语法格式:
EXPOSE <端口>/<协议>
- 仅仅只是申明,仍然需要
docker run -p
来映射端口,docker run -p <宿主机端口>:<容器内端口>/<协议>
可以覆盖容器内申明的协议
EXPOSE 80/tcp
EXPOSE 80/udp
CMD 和 ENTRYPOINT
容器启动时需要执行的命令ENTRYPOINT 语法格式:
ENTRYPOINT ["<可执行文件>","<参数1>","<参数2>"]
ENTRYPOINT ["<shell命令>","<参数1>","<参数2>"]
CMD 语法格式:
CMD ["<可执行文件>","<参数1>","<参数2>"]
CMD ["<shell命令>","<参数1>","<参数2>"]
CMD ["<参数1>","<参数2>"]
# 只有设置了ENTRYPOINT
指令才可以使用这种方式
CMD
和ENTRYPOINT
之间协作的规则至少指定一个
CMD
或ENTRYPOINT
指令- 多条
CMD
或ENTRYPOINT
都只有最后一条生效
- 多条
最好是将
CMD
作为ENTRYPOINT
的参数来使用- 因为
docker run
命令带入的启动参数会覆盖CMD
,而不会覆盖ENTRYPOINT
- 因为
CMD
和RUN
是有区别的CMD
是容器运行的时候要执行的命令RUN
是容器构建的时候要执行的命令
全剧终
- 其实 Dockerfile 里面还有其他指令,比如
VOLUME
、ONBUILD
、STOPSIGNAL
、HEALTHCHECK
、SHELL [看官方介绍,应该是对与 windows 的 powershell 比较友好]
,感兴趣的可以去官方看资料(留点头发) 在官方文档,有提及到 Dockerfile cache 机制,.dockerignore 文件 等一些功能,后期有机会在学习(留点头发)
- 再次附上官方链接
- dockerfile docs
- Dockerfile 最佳实践指南