0. 为什么需要 Dockerfile

  • 公共镜像不符合项目需求;
  • 自研系统没有公共镜像。

    1. 制作镜像的方法

    1.1 基于容器

    1.2 Dockerfile

    Dockerfile 是一个文本文件包含了构建一个镜像的所有命令。

    2. Dockerfile Format

  • format

    • Commnet 注释

    • INSTRUCTION arguments 指令和参数(指令本身并不区分大小写,建议使用大写)
  • dockerfile 中的指令是顺序执行的
  • 第一个非注释行必须是 FROM 指令,用来指定 Base Image
  • Dockerfile 需要一个专用的工作目录,并且 Dockerfile文件名首字母必须大写,镜像需要的任何文件都需要放在 Dockerfile 专用的工作目录下。
  • .dockerignore 文件中定义的文件将不会被打包至镜像中
  • 在 Dockerfile json数组中需要使用双引号 ""

    3. Dockerfile Instructions

    FROM

    FROM 指令是最重要的一个并且必须为 Dockerfile 文件的第一个非注释行指令,用于为镜像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境。
    实践中,基准镜像可以是任何可用镜像文件,默认情况下,Dockerfile 会在 docker 主机上查找指定的镜像文件,当镜像文件不存在时,从 Docker Hub Registry 上拉取所需的镜像文件。如果找不到指定的镜像文件,docker build 会返回一个错误信息。
  1. FROM <repository>[:<tag>]
  2. FROM <repository>@<digest>
  • :指定作为 base image 的名称
  • :base image 的标签,可选项,默认为 latest

    MAINTAINTER(deprecated已废弃)

    用于让 Dockerfile 制作者提供制作者本人的详细信息。Dockerfile 并不限制 MAINTAINER 指令可出现的位置,但推荐其放置于 FROM 指令之后
MAINTAINER <author's detail>

参数说明
<author’s detail>:任何文本信息,约定俗成地使用作者名称及邮件地址

LABEL

LABEL 指令可以对镜像添加相关的元数据。

LABEL <key>=<value> <key>=<value> <key>=<value> ...
  • 一个 LABEL 是一个键值对,一个 LABEL 指令也可以同时添加多个键值对,一个 Dockerfile 可以有多个 LABEL 指令;
  • value 中包含空格需要用引号;

    COPY

    用于从 Docker 主机复制文件至创建的新镜像文件中。
COPY <src> ... <dest> 或
COPY ["<src>",..."<dest>"]

参数说明

  1. :要复制的源文件或目录,支持使用通配符
  2. :目标路径,即正在创建的 image 的文件系统路径;建议为使用绝对路径,否则 COPY 指令则以 WORKDIR 为起始路径

文件复制准则

  1. 必须是 build 上下文中的路径,不能是其父目录中的文件;
  2. 如果是目录,则其内部文件或子目录会被自动递归复制,但目录本身并不会被复制;
  3. 如果制定了多个或在中使用了通配符,则必须是一个目录,且必须以 / 结尾
  4. 如果不存在,将会自动创建,包括其父目录路径;

示例
复制单个文件

# Description:myimage
FROM busybox:latest
MAINTAINER "Manson <chrisma1209@gmail.com>"
# LABEL maintainer="Manson <chrisma1209@gmail.com>"
COPY index.html /data/web/html/

docker build -t tinyhttpd:v0.1-1 /root/myimage
docker image ls
docker run --rm --name tinyweb1 tinyhttpd:v0.1-1 cat /data/web/html/index.html
此命令用于测试制作的镜像是否已成功拷贝文件,并且修改容器启动运行的默认命令,修改为 cat 命令,cat 命令执行结束,容器结束并删除。这是测试容器时的常用方法。

复制目录

# Description:myimage
FROM busybox:latest
MAINTAINER "Manson <chrisma1209@gmail.com>"
# LABEL maintainer="Manson <chrisma1209@gmail.com>"
COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/ #仅实验用,因为每一条指令都会生成一个新的文件系统层
cp -r /etc/yum.repos.d/ /root/myimage
tree -L 2       
.
├── Dockerfile
├── index.html
└── yum.repos.d
    ├── CentOS-Base.repo
    ├── CentOS-CR.repo
    ├── CentOS-Debuginfo.repo
    ├── CentOS-fasttrack.repo
    ├── CentOS-Media.repo
    ├── CentOS-Sources.repo
    ├── CentOS-Vault.repo
    ├── docker-ce.repo
    ├── epel.repo
    ├── epel-testing.repo
    └── google-cloud.repo
docker build -t tinyhttpd:v0.1-2 /root/myimage
docker image ls
docker run --rm --name tinyweb1 tinyhttpd:v0.1-2 ls /etc/yum.repos.d

ADD

ADD 指令类似于 COPY指令,ADD 支持使用 TAR 文件和 URL 路径

ADD <src> ... <dest> 或
ADD ["<src>",..."<dest>"]

说明

  1. 同 COPY 指令;
  2. 如果为 URL 且不以/结尾,则指定的文件将被下载并直接被创建为;如果以/结尾,则文件名 URL 指定的文件将被直接下载并保存为/;
  3. 如果是一个本地文件系统上的压缩格式的 tar 文件,它将被展开为一个目录,其行为类似于 tar -x 命令,通过 URL 获取的 TAR 文件不会被自动展开;
  4. 如果 有多个,或其简介或直接使用了通配符,则 必须是一个以/结尾的目录路径;如果 不以/结尾,则其被视作为一个普通文件,的内容将被直接写入到

示例

# Description:myimage
FROM busybox:latest
MAINTAINER "Manson <chrisma1209@gmail.com>"
# LABEL maintainer="Manson <chrisma1209@gmail.com>"
COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/
#ADD http://nginx.org/download/nginx-1.15.2.tar.gz /usr/local/src/ #URL 下载不会展开
ADD nginx-1.15.2.tar.gz /usr/local/src/ #宿主机上先下载好,在镜像文件系统中会自动展开
# 使用 URL 下载 tar 文件包不会被自动解压
docker build -t tinyhttpd:v0.1-3 /root/myimage/
docker run --rm --name tinyweb1 tinyhttpd:v0.1-3 ls /usr/local/src
# 使用本地的 tar 文件包,会被自动展开
wget http://nginx.org/download/nginx-1.15.2.tar.gz
docker build -t tinyhttpd:v0.1-4 /root/myimage/
docker run --rm --name tinyweb1 tinyhttpd:v0.1-4 ls /usr/local/src/nginx-1.15.2

WORKDIR

用于为 Dockerfile 中所有的 RUN、CMD、ENTERPOINT、COPY和 ADD 指令设定工作目录

WORKDIR <dirpath>

在 Dockerfile 文件中,WORKDIR 指令可出现多次,其路径也可以为相对路径,不过相对此前一个 WORKDIR 指令指定的路径。
另外,WORKDIR 也可以调用由 ENV 指定定义个变量,如 WORKDIR $STATEPATH
示例

...
WORKDIR /usr/local/
ADD nginx-1.15.2 ./src/ #./目录就使用的WORKDIR 定义个目录
...

VOLUME

用于在 image 中创建一个挂载点目录,以挂载 Docker host 上的卷或其他容器上的卷。

VOLUME <mountpoint> 或
VOLUME ["<mountpoint>"]

如果挂载点目录路径下此前有文件存在,docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中。
示例

FROM busybox:latest
ADD nginx-1.15.2.tar.gz /usr/local/src/
VOLUME /data/mysql/
docker build -t tinyhttpd:v0.1-5 /root/myimage/
docker run --rm --name tinyweb1 tinyhttpd:v0.1-5 mount 或
docker run --rm --name tinyweb1 tinyhttpd:v0.1-5 sleep 60
docker inspect

EXPOSE

用于为容器打开指定要监听的端口实现与外部通信

EXPOSE <port>[/<protocol>] [<port>[/<protocol>]...]

参数说明

  • 用于指定传输层协议,可为 tcp 或 udp 二只之一,默认为 tcp 协议

EXPOSE 指令可以一次指定多个端口,如 EXPOSE 11211/udp 11211/tcp
需要注意的是,EXPOSE 指定是待暴露的端口,运行容器默认不会暴露端口,使用 docker run 命令-P 选项后,会暴露 Dockerfile 中指定的暴露端口。
示例

# Description:myimage
FROM busybox:latest
MAINTAINER "Manson <chrisma1209@gmail.com>"
# LABEL maintainer="Manson <chrisma1209@gmail.com>"
COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/
#ADD http://nginx.org/download/nginx-1.15.2.tar.gz /usr/local/src/
ADD nginx-1.15.2.tar.gz /usr/local/src/
VOLUME /data/mysql/
EXPOSE 80/tcp
docker run --name tinyweb1 --rm tinyhttpd:v0.1-6 /bin/httpd -f  -h /data/web/html #前台运行 docker 容器
docker inspect tinyweb1 #查看当前容器的 IP地址
curl 容器的 IP地址 #验证端口是否暴露,可以访问网页因为容器和宿主机在一个桥上,默认不会暴露端口
docker port tinyweb1
docker kill tinyweb1 #停止前台运行的容器
docker run --name tinyweb1 --rm -P tinyhttpd:v0.1-6 /bin/httpd -f  -h /data/web/html #-P 选项表示暴露应该暴露的端口
docker port tinyweb1

ENV

用于为镜像定义所需的环境变量,并可被 Dockerfile 文件中位于其后的其他指令如 ENV、ADD、COPY 等所调用。调用格式为 $variable_name${variable_name}

使用环境变量替换 (Environment replacement) 引用变量 $variable_name 或${variable_name} ${variable:-word}:变量默认值,如果 variable 值为空或未设置,就使用 word 作为变量值。 ${variable:+word}:变量有值就显示为 word,变量无值,就显示无值。

ENV <key><value>或
ENV <key>=<value>...

说明

  1. 在第一种格式中,之后的所有内容均会被视作其的组成部分,因此,一次只能设置一个变量;
  2. 第二种格式可以一次设置多个变量,每个变量为一个”=“的键值对,如果中包含空格,可以以反斜线(\)进行转义,也可以通过对加引号进行标识;另外,反斜线也可以用于续行。
  3. 定义多个变量时,建议使用第二种方式,以便在同一层中完成所有功能。

示例

FROM busybox:latest

ENV DOC_ROOT /data/web/html/ \
    WEB_SERVER_PACKAGE="nginx-1.15.2"

COPY index.html ${DOC_ROOT:-/data/web/html/}
ADD ${WEB_SERVER_PACKAGE}.tar.gz /usr/local/src/
...

变量值生效的阶段
image.png
在容器启动时,可以修改 ENV 的值,但是有些 ENV 的值是不会影响 Dockerfile 中定义的 ENV值,这里要区分 ENV 值的生效阶段,在第一阶段docker build生效的 ENV 值在容器启动前操作,不会被影响。

RUN

RUN 指令运行于第一阶段 docker build(镜像文件构建过程)。RUN命令可执行的操作取决于基础镜像支持的命令数量。

RUN <command> 或
RUN ["<executable>","<param1>", "<param2>"]

说明

  1. 在第一种格式中,通常是一个 shell 命令,且以 /bin/sh -c 来运行它,这意味着此进程在容器中的 PID 不为 1,不能接收 Unix 信号,因此,当使用 docker stop 命令停止容器时,此进程接收不到 SIGTERM 信号;
  2. 第二种格式中的参数是一个 JSON 格式的数组,其中为要运行的命令,后面的为传递给命苦的选项或参数;然而,此种格式的命令不会以 /bin/sh -c 来发起,因此常见的 shell 操作如变量替换以及通配符(?,*等)替换不会进行;不过,如果要运行的命令依赖于此类 shell 特性时,可以将其替换为类似下面的格式: RUN ["/bin/sh","-c","executable","paramN"]
...
ENV WEB_SERVER_PACKAGE=nginx-1.15.2
ADD http://nginx.org/download/nginx-1.15.2 /usr/local/src/
RUN cd /usr/local/src/ &&  tar -xf ${WEB_SERVER_PACKAGE}.tar.gz && mv nginx-* webserver
...

CMD

类似于 RUN 指令,运行于第二阶段 docker run(基于 Dockerfile 构建出的新镜像文件启动的容器),需要结合 ENTRYPOINT 使用。
在 Dockerfile 中CMD建议只出现一次,如果出现多次,那么只有最后一个 CMD 有效。
CMD 指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD 指令的命令可以被 docker run 的命令行参数所覆盖。

CMD <command> 或
CMD <"<executable>","<param1>","<param2>"> 或
CMD <"<param1>","<param2>">

参数说明

  • 前两种语法格式的意义同 RUN
  • 第三种语法用于为 ENTRYPOINT 指令提供默认参数

示例

  1. 创建一个新的目录并创建 Dockerfile ```dockerfile FROM busybox:latest LABEL maintainer=”Manson chrisma1209@gmail.com“ app=”httpd”

ENV WEB_DOC_ROOT=”/data/web/html/“

RUN mkdir -p $WEB_DOC_ROOT && echo ‘

Busybox httpd server.

‘ > ${WEB_DOC_ROOT}/index.html

CMD /bin/httpd -f -h ${WEB_DOC_ROOT}

2.创建镜像

```shell
docker build -t tinyhttpd:v0.2-1 /root/img2

3.查看镜像信息

docker image inspect tinyhttpd:v0.2-1
...
"Cmd": [
  "/bin/sh",
  "-c",
  "#(nop) ",
  "CMD [\"/bin/sh\" \"-c\" \"/bin/httpd -f -h ${WEB_DOC_ROOT}\"]"
...

这里可以看到,容器先启动了/bin/sh,然后在运行 /bin/httpd,就就是说 httpd 是 shell 的子进程
4.运行容器

docker run --name tinyweb2 -it --rm -P tinyhttpd:v0.2-1

即使使用了-it 选项,但是因为没有进入交互式环境,因为 httpd 根本不提供交互式接口
5.进入容器查看进程

docker exec -it tinyweb2 /bin/sh
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/httpd -f -h /data/web/html/
   11 root      0:00 /bin/sh
   16 root      0:00 ps
/ # printenv
WEB_DOC_ROOT=/data/web/html/
HOSTNAME=aa6643a0ceaf
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/

这里有一个问题,不是应该/bin/sh 的 PID是 1 ,而目前 httpd 的进程号为 1,可以接收 docker stop 发送的信号,因为 docker 进行过转换,通过 docker image inspect tinyhttpd:v0.2-1 可以查看。
6.修改 Dockerfile

...
CMD ["/bin/httpd","-f","-h ${WEB_DOC_ROOT}"]

这样构建镜像并启动容器,由于并没有创建 sh 子进程,所以也获取不到shell变量的值,因此会报错,找不到路径。所以要修改为:

FROM busybox:latest
LABEL maintainer="Manson <chrisma1209@gmail.com>" app="httpd"

ENV WEB_DOC_ROOT="/data/web/html/"

RUN mkdir -p $WEB_DOC_ROOT && echo '<h1>Busybox httpd server.</h1>' > ${WEB_DOC_ROOT}/index.html

#CMD /bin/httpd -f -h ${WEB_DOC_ROOT}

CMD ["/bin/sh","-c","/bin/httpd -f -h ${WEB_DOC_ROOT}"]

注意的是-c 后面的参数是一个参数只需要一个对引号,分开写会报错。

ENTRYPOINT

类似 CMD命令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序。与 CMD 不同的是,由 ENTRYPOINT 启动的程序不会被 docker run 命令行指定的参数所覆盖,而且,这些命令行参数会被当做参数传递给 ENTRYPOINT指定的程序。【参数追加】
docker run 命令句的 --entrypoint 选项可以覆盖 ENTRYPOINT 指令指定的程序

ENTRYPOINT <command>
ENTRYPOINT ["<executable>","<param1>","param2"]

说明

  1. docker run 命令传入的命令参数会覆盖 CMD 指令的内容并且附加到 ENTRYPOINT 命令最后作为其参数使用;
  2. Dockerfile文件中也可以存在多个 ENTRYPOINT 指令,但仅有最后一个会生效。
  3. CMD 和 ENTRYPOINT 同时在一个 Dockerfile 时,CMD 指定的参数作为 ENTRYPOINT的参数

示例 1

FROM busybox:latest
LABEL maintainer="Manson <chrisma1209@gmail.com>" app="httpd"
ENV WEB_DOC_ROOT="/data/web/html/"
RUN mkdir -p $WEB_DOC_ROOT && echo '<h1>Busybox httpd server.</h1>' > ${WEB_DOC_ROOT}/index.html
ENTRYPOINT /bin/httpd -f -h ${WEB_DOC_ROOT}
docker build -t tinyhttpd:v0.2-5 /root/img2/
#正常运行
docker run --name tinyweb -it --rm -P tinyhttpd:v0.2-5
##正常运行,没有执行 ls 命令,ls 命令作为参数追加到 ENTRYPOINT 指令指定的程序之后
docker run --name tinyweb -it --rm -P tinyhttpd:v0.2-5 ls /data/web/html

示例 2

  1. 创建目录和 Dockerfile 文件
FROM nginx:1.14-alpine
LABEL maintainer="Simon <chrisma1209@gmail.com>"

ENV NGX_DOC_ROOT='/data/web/html/'

ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/

CMD ["/usr/sbin/nginx","-g","daemon off;"]

#先运行/bin/entrypoint.sh 脚本,然后引用 CMD 指定的参数,并替换/bin/entrypoint.sh 进程
#ENTRYPOINT ["/bin/sh","-c","/bin/entrypoint.sh"]
#ENTRYPOINT ["/bin/sh","-c","/bin/entrypoint.sh"]
#ENTRYPOINT ["/bin/sh","-c","ls /bin"]
ENTRYPOINT ["/bin/entrypoint.sh"]
  1. 准备相关文件

entrypoint.sh

#!/bin/sh
#filenname entrypoint.sh
#
cat > /etc/nginx/conf.d/www.conf << EOF
server {
        server_name $HOSTNAME;
        listen ${IP:-0.0.0.0}:${PORT:-80};
        root ${NGX_DOC_ROOT};
}
EOF

exec "$@" #替换当前进程

index.html

<h1>mynginx in docker</h1>
  1. 构建镜像

docker build -t myweb:v0.1-12 /root/mynginx/

  1. 运行容器

docker run --name myweb -it --rm -P myweb:v0.1-12
如果容器运行失败,提示 permission denied,在构建镜像前手动赋予entrypoint.sh执行权限

  1. 验证

docker exec -it myweb /bin/sh 进入容器,查看监听的端口,查看进程,获取网页 wget -O - -q ab04ddac4745

  1. 传递参数

docker run --name myweb -it --rm -P -e "PORT=8080" myweb:v0.1-12
为了在不同的环境中使用相同的镜像,通常都是使用 entrypoint 脚本启动,当传入不同的参数来区分在不同环境中使用容器,如区分开发环境、测试环境、生产环境。Dockerfile 中 entrypoint 脚本的作用、意义和参数的传递方式是一个难点,可以在github 上 docker 仓库中多多学习其他镜像Dockerfile 的写法。

USER

用于指定运行 image 时或运行 Dockerfile 中任何 RUN、CMD或 ENTRYPOINT 指令指定程序时的用户名或 UID。
默认情况下,container 的运行用户是 root 用户。

USER <UID>|<USERNAME>

说明
可以为任意数字,但实践中必须为/etc/passwd 中某用户的有效 UID,否则,docker run 命令将运行失败

HEALTHCHECK

用于指定一个命令检查主进程的工作状态

HEALTHCHECK [OPTIONS] CMD command
HEALTHCHECK NONE

OPTIONS

  • —interval=DURATION(默认 30 秒)
  • —timeout=DURATION(默认 30 秒)
  • —start-period=DURATIPN(默认 0 秒),容器启动时的等待时间,等待后开始健康检查
  • —retries=N(默认 3)

响应值

  • 0:success,容器健康并且可提供服务
  • 1:unhealthy:容器不能正常工作
  • 2:reserverd:没有,可自定义

示例

HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/|| exit 1

SHELL

指定默认 shell

STOPSIGNAL

STOPSIGNAL signal

ARG

定义变量,在 docker build 过程中使用的变量。而 ENV 的变量是可以在 docker run 时进行传值的。

ONBUILD

用于在 Dockerfile 中定义一个触发器。Dockerfile 用于 build 镜像文件,此镜像文件亦可作为base image 被另外一个 Dockerfile 用作 FROM 指令的参数,并以之构建的镜像文件。
在后面的这个 Dockerfile 中的 FROM 指令在 build 过程中被执行,将会“触发”创建其 base image 的 Dockerfile 文件中的 ONBUILD 指令定义的触发器。

ONBUILD <INSTRUCTION>

说明
尽管任何指令都可注册成为触发器指令,但 ONBUILD 不能自我嵌套,且不会触发 FROM 和 MAINTAINER 指令。
使用包含 ONBUILD 指令的 Dockerfile 构建的镜像应该使用特殊的标签,如 ruby:2.0-onbuild。
在 ONBUILD 指令中使用的 ADD 或 COPY 指令应该格外小心,因为构建过程中的上下文在缺少指定的源文件时会失败。