使用 Dockerfile 构建镜像时常见有如下指令:

  1. FROM 添加基础镜像
  2. MAINTAINER 添加作者信息
  3. LABEL 为镜像添加Label元数据,docker inspect 可查看
  4. RUN 构建过程中要运行的命令
  5. CMD 指定容器启动时要运行的命令
  6. ENTRYPOINT CMD类似,但有细微区别
  7. ADD 将构建目录下的文件复制到容器内
  8. COPY ADD类似,但有细微区别
  9. EXPOSE 指定运行该镜像容器使用的端口
  10. VOLUME 为容器添加卷
  11. WORKDIR 设置一个工作目录
  12. ENV 设置环境变量
  13. ARG 定义build时传递给构建运行时的变量
  14. USER 指定用户,不指定默认为root
  15. ONBUILD 添加触发器
  16. STOPSIGNAL 指定停止容器时使用的系统信号

更多信息参考:
https://docs.docker.com/engine/reference/builder/

RUN、CMD、ENTRYPOINT解析

1号进程之争:exec模式和shell模式

关于RUN指令
RUN 主要在build时,在现有的基础上新添一层layer,创建一个新的image。(一般用来安装软件包)
比如前面的第一个Dockerfile中的例子:
RUN apt-get update && apt-get install -y nginx
RUN echo ‘Hi, I am in your container’ > /usr/share/nginx/html/index.html

关于CMD

FROM alpine:3.9
CMD [ "top" ]
# 如果是这种模式,容器中任务进程就是1号进程,但没有环境变量,如 $HOME

FROM alpine:3.9
CMD [ "sh", "-c", "echo $HOME" ]
# 但这种就是通过shell来运行了, 相当于下面的代码

FROM alpine:3.9
CMD top

PS1:作为启动命令,一个dockerfile中只能有一个CMD,如果有多个,那么只有最后一个会生效。
PS2:docker run -i -t testimage:v1 /bin/ps 中 docker run 会覆盖掉CMD指令

ENTRYPOINT 也有 exec 和 shell 模式,相较于CMD,它不容易被覆盖

示例:
ENTRYPOINT ["/usr/sbin/nginx"]
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]

ENTRYPOINT 还可以和 CMD 配合使用:

ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]

# 这样CMD可以将 -h 选项传递给 ENTRYPOINT,最终执行 /usr/sbin/nginx -h
# 也可以实现 docker run 命令将 -h 选项覆盖掉

参考一下:
https://zhuanlan.zhihu.com/p/30555962

COPY 和 ADD 解析

COPY 和 ADD 两个指令也非常相似,如下写法也会将 dirtocp/ 下的所有子文件 (包括子目录但不包括 dirtocp 本身)添加到容器内指定目录下:

# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER DaFei "fly1248@hotmail.com"
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
EXPOSE 80
ADD dirtocp/ /usr/share/nginx/html/
COPY dirtocp/ /usr/share/nginx/                # 这里 ADD 和 COPY 指令没有区别
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
WORKDIR /usr/share/nginx/html/

但如果是对压缩包 file.tar.gz,ADD 指令会将解压后内容添加到目标目录,而COPY指令会直接将压缩包复制到目标目录,没有解压动作。

ADD file.tar.gz  /usr/share/nginx/html/
COPY file.tar.gz /usr/share/nginx/

ENV 和 ARG 指令

ENV 指令用来在镜像构建过程中设置环境变量。设置的环境变量可以在后续任何RUN指令中引用。

ENV RVM_PATH /home/rvm/
RUN gem install unicorn

# 使用 ENV 设置多个环境变量
ENV RVM_PATH=/home/rvm/ RVM_ARCHFLAGS="-arch i386"

# 在其他指令中使用环境变量
ENV TARGET_DIR /opt/app
WORKDIR $TARGET_DIR

ARG 指令用来定义可以在docker build命令运行时传递给构建运行时的变量。在构建时使用 —build-arg 标记。

# 添加ARG指令
ARG build
ARG webapp_user=user

# 使用ARG指令
docker build --build-arg build=1234 -t dafei/webapp .
# 这里构建webapp镜像时,build变量会设置为1234,而webapp_user变量则继承默认值user

Docker 预定义了一组 ARG变量,可以在构建时直接使用

HTTP_PROXY     (或 http_proxy)
HTTPS_PROXY (或 https_proxy)
FTP_PROXY     (或 ftp_proxy)
NO_PROXY        (或 no_proxy)

要使用这些预定义的变量,只要给 docker build 命令传递 —build-arg =

USER 指令

USER nginx

该指令使对应容器以nginx用户的身份来运行。除了用户名,我们还可以指定用的UID,组名,GID或者俩者组合

USER user
USER user : group
USER uid
USER uid : gid
USER user : gid
USER uid : group

# docker run -it --name mynginx7 nginx:v7 /bin/bash
nginx@c71e3a4b5f94:/usr/share/nginx/html$ id
uid=1000(nginx) gid=1000(nginx) groups=1000(nginx)

PS:使用USER指令前,要确保用户存在,可以用指令 RUN useradd nginx 添加该用户。否则构建可能没有问题,运行容器时会报错:
docker: Error response from daemon: unable to find user nginx: no matching entries in passwd file.
ERRO[0000] error waiting for container: context canceled

PS:如果不指定用户,默认用户都是root。

LABEL 指令

用于为镜像添加元数据,以键值对形式展现。

LABEL version="1.0"
LABEL location="New York" type="Data Center" role="Web Server"
LABEL tested=true

构建之后使用 docker inspect 命令查看镜像详情可以看到 Labels:

"Labels": {
                "location": "New York",
                "role": "Web Server",
                "tested": "true",
                "type": "Data Center",
                "version": "1.0"
            }

ONBUILD 指令

ONBUILD指令为镜像添加触发器,当镜像被用做其他镜像的基础镜像时,该镜像中的触发器将被执行。
触发器会在构建过程中插入新指令,我们可以认为这些指令是紧跟在FROM之后指定的。

ONBUILD ADD . /app/src
ONBUILD RUN cd /app/src && make

示例:
FROM ubuntu:16.04
MAINTAINER Dafei
RUN apt-get update && apt-get install -y apache2
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ONBUILD ADD . /var/www/
EXPOSE 80
ENTRYPOINT ["/usr/sbin/apache2"]
CMD ["-D", "FOREGROUND"]

docker build -t apache:v1 .

...

Step 7/10 : ONBUILD ADD . /var/www
 ---> Running in 85019ce138aa
Removing intermediate container 85019ce138aa
 ---> 454c282c23a7
...

# ONBUILD 指令也可以用 docker inspect 命令来查看
docker inspect apache:v1

...
 "OnBuild": [
                "ADD . /var/www"
            ],
...

# 然后我们基于该模板来构建新的镜像:
FROM apache:v1
MAINTAINER Dafei
ENV APPLICATION_NAME webapp
ENV ENVIRONMENT development

# docker build -t "webapp:v1" .
Step 1/4 : FROM apache:v1
# Executing 1 build trigger
 ---> 2c3284e3ca94
# 步骤1 会执行之前定义的触发器 "ADD . /var/www"

# 通过新镜像来运行容器
docker run --name mywebapp -d webapp:v1

# docker exec -it mywebapp /bin/bash
root@3adfda48cfe4:/# cd /var/www
root@3adfda48cfe4:/var/www# ll
total 28
drwxr-xr-x 1 root root 4096 Apr 12 13:41 ./
drwxr-xr-x 1 root root 4096 Apr 12 13:06 ../
-rw-r--r-- 1 root root   89 Apr 12 13:09 Dockerfile
-rw-r--r-- 1 root root  224 Apr 12 13:36 Dockerfile.gz
drwxr-xr-x 2 root root 4096 Apr 12 13:06 html/
-rw-r--r-- 1 root root   24 Apr 12 12:56 index.htmle

# 进入容器我们发现"ADD . /var/www"指令执行的结果

VOLUME 指令

可以创建一个匿名数据卷挂载点,一般用于存放数据库和需要持久化的数据。

特性:

  • 卷可以在容器间共享和重用
  • 卷修改不对更新镜像产生影响
  • 卷会一直存在到没有任何容器再使用它

示例:

VOLUME ["/data"]
VOLUME ["/opt/project", "/data"]
VOLUME /opt/project /data

这里向 /data 或 /opt/project 中写入的信息都不会记录进入容器的存储层
PS:docker cp 命令是和 VOLUME 指令相关的实用命令,可以从容器负载文件,或复制文件到容器上。

用 docker inspect 命令查看镜像的 VOLUME 指令信息:

# docker inspect nginx:v9
...
   "Volumes": {
                "/mydata": {},
                "/opt/prOject,": {}
            },
...

通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,而是自动生成的。
通过镜像运行容器后,我们仍然可以通过 docker inspect 命令来查看卷的挂载信息:

# docker inspect mynginx9
...
        "Mounts": [
            {
                "Type": "volume",
                "Name": "bcc2b294f19dace0a20f945ec3d8a94fd821e3d33aacba42bddc22a4bd66a397",
                "Source": "/var/lib/docker/volumes/bcc2b294f19dace0a20f945ec3d8a94fd821e3d33aacba42bddc22a4bd66a397/_data",
                "Destination": "/opt/project,",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            },
            {
                "Type": "volume",
                "Name": "72ba49a23d9ce5ee1d026a17cfac10831126a7b7788139b8d90e35e2d11907cb",
                "Source": "/var/lib/docker/volumes/72ba49a23d9ce5ee1d026a17cfac10831126a7b7788139b8d90e35e2d11907cb/_data",
                "Destination": "/mydata",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
...

或:
# docker inspect mynginx9 -f {{.Mounts}}
[{volume 9df48a661984521f1731965eb298ff5d1440677c56a503fdfc0d213852a65a7b /var/lib/docker/volumes/9df48a661984521f1731965eb298ff5d1440677c56a503fdfc0d213852a65a7b/_data /opt/project local  true }]

WORKDIR 指令

从镜像创建一个新容器时,WORKDIR指令可以在容器内设置一个工作目录。RUN、ENTRYPOINT 或 CMD 指令指定的程序会在这个目录下执行。

WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackup"]

# 执行 docker exec/run -it ... /bin/bash 命令时,也将会在 WORKDIR 下执行,
# 不过可以使用 -w 选项在运行时覆盖工作目录:

# docker run -it -w /var/log ubuntu pwd
/var/log

镜像优化参考:
https://devopscube.com/reduce-docker-image-size/