今天我们来学习一下利用dockerfile 来构建镜像。

   我们知道 docker comit 命令也可以构建一个新的镜像,但是这个操作大部分情况下是不可控制的,这个操作对镜像的操作都是黑箱操作,除了制作镜像的人知道执行过什么命令,怎么生成的镜像,其他的人不知道。 而且: docker commit 这个操作,是在原有镜像的基础上,每执行一次命令,就会在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。

   这样如果使用docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次。

   所以我们要使用dockerfile 来构建镜像, 按照官方文档的解释,dockerfile是一个文本文件,这个文本文件包含了用户想聚合或者配置到镜像里面所需要的所有指令。

怎么使用?

   使用docker build 命令来构建镜像,但是这个命令需要一个Dockerfile文件 和 上下文 (或者直接理解为一个目录的内容吧,po主自己加的)。 构建上下文指的是在本地路径 PATH 或者 URL 范围内的所有文件。 PATH 指的是本地文件系统, URL 特指Git 仓库地址。

   当前目录是这样的,存在两个文件:

1
2
$ls
Dockerfile Dockerfile.ubuntu

   比如我们现在运行

1
2
3
$docker build .
#这个命令没有指定Dockerfile的位置,你可以使用docker build --help 查看
#缺省Dockerfile 是PATH/Dockerfile ./Dockerfile 是存在的这个文件的

   它做了什么呢,执行构建的是Docker守护进程,而不是docker cli,做的第一件事就是将.(当前整个目录)的文件发给Docker daemon。 大部分情况下,应该将Dockerfile放到一个空的目录下面,最好只放一个Dockerfile文件。(警告: 当然不要使用根目录,如果将dockerfile放到根目录/,构建的时候,会将整个硬盘的所有内容copy一份到Docker daemon)。还好docker 有.dockerignore文件,类似.gitignore,此处省略多字,相信你能明白。

   当然我们也可以指定Dockerfile的路径

1
$docker build -f /path/in/you/any/local/filesystem/Dockerfile

   可以构建不同tag的多个镜像,like this(本机测试):

1
docker build . -t nginx:v333.01 -t ngnix:v333.02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$docker build . -t nginx:v333.01 -t ngnix:v333.02
Sending build context to Docker daemon 3.072 kB
Step 1/3 : FROM nginx
---> 958a7ae9e569
Step 2/3 : CMD echo $HOME
---> Running in 1ad7f1dd9964
---> 66e037162e28
Removing intermediate container 1ad7f1dd9964
Step 3/3 : RUN echo '<h1> Hello. Docker ! second version </h1>' > /usr/share/nginx/html/index.html && echo 'what s going on'
---> Running in 7ae3b76417c6
what s going on
---> d4e9bdc765e4
Removing intermediate container 7ae3b76417c6
Successfully built d4e9bdc765e4

   最终的结果:

1
2
3
4
$docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v333.01 d4e9bdc765e4 30 seconds ago 109 MB
ngnix v333.02 d4e9bdc765e4 30 seconds ago 109 MB


   我们来看 Dockerfile的格式。

1
INSTRUCTION arguments

   这里的INSTRUCTION指令有多个关键字: FROM, RUN, CMD, LABEL, EXPOSE, ENV, ADD, COPY, ENTRYPOINT

ENV

   我们先说ENV,env 大白话可以理解为环境变量。语法是这样:

1
2
3
4
5
6
7
ENV <key> <value>
ENV <key> = <value>
#这样声明,这种声明方式只能声明单个变量
ENV usr root
#这种声明方式可以声明多个环境变量
ENV usr=root pass=pwd

FROM

   顾名思义就是我们要构建的镜像的来源或者基础,语法是这样:

1
2
3
4
5
FROM <image>
#or
FROM <image>:<tag>
#or
FROM <image>@<digest>

RUN

   RUN 命令是制作镜像里面常用的命令,有两种写法

1
2
RUN <command>
RUN ["executable", "param1""param2"]

tips: Dockerfile中的每一个指令都会在镜像上面新增一层中间层镜像,直至最外层镜像,所以执行RUN指令的时候,可以合并多个RUN 指令,最好不要一次性写多个,请看下面的操作

1
2
3
4
5
6
# bad way
RUN echo hello
RUN echo docker
# good way
RUN echo hello \
&& echo docker

CMD

  Docker不是虚拟机,容器就是进程,如果是进程,进程启动的时候,就需要指定启动程序或者参数。 在Dockerfile里面只能有一个CMD,如果有多个的话,则只有最后一个CMD才会生效。CMD的主要目的为正在运行的容器提供一些默认的值或者行为。有三种形式(一般推荐exec 格式):

  • CMD [“executable”,”param1”,”param2”] (exec form, this is the preferred form)
  • CMD [“param1”,”param2”] (as default parameters to ENTRYPOINT)
  • CMD command param1 param2 (shell form)

   tip: 第一种和第三种方式的命令,在docker run 的时候 后面可以执行其他命令,将这个命令给覆盖掉。比如我们Dockerfile是这样写的

1
2
FROM ubuntu:latest
CMD /bin/bash

  或者

1
2
FROM ubuntu:latest
CMD [ "sh", "-c", "echo $HOME" ]

我们这样运行: docker run -it --rm ubuntu:v4 cat /etc/os-release, 后面的 cat /etc/os-release将默认的CMD命令给替换了,打印的结果长这样:

1
2
3
4
5
6
7
8
9
10
11
12
$docker run -it --rm ubuntu:v4 cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.2 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.2 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial

LABEL

   LABEL指令可以像镜像添加metadata元数据,和ENV一样是key-value的形式

1
LABEL <key>=<value> <key>=<value> <key>=<value> ...

   LABELRUN指令一样,docker官方推荐将多个labels合并到一个单独的LABEL如果可能的话,每一条LABEL指令都会添加一个新的层,最后都会生成多个无效的中间层镜像,若存在多个同名的key,则最后一个会覆盖之前的key。

EXPOSE

   这个不是暴露端口,该指令告知Docker这个容器在运行的时候监听的那些端口。这并不会让HOST访问到容器的这些端口,如果你要达到这个目的,要使用-p参数,比如我们启动一个nginx:

1
$docker run -d -p 81:80 --name nginx-web nginx:latest

   这样就会暴露容器的80端口给host的81端口,可以直接localhost:81访问container的nginx服务。也可以不这样做,比如我们在Dockerfile里面声明了多个端口: 80 90

   在启动容器的时候这样写 docker run -d -P –name container-name image:tag 这样会自动将container的90 80端口暴露给host。我们可以通过docker ps 查看ports这一项。

ENTRYPOINT

   它和CMD 一样,都是指定容器的启动程序以及参数,但是和CMD 不同的是替换起来,要加一个参数 --entrypoint

   两种形式:

  • ENTRYPOINT [“executable”, “param1”, “param2”] (exec form, preferred)
  • ENTRYPOINT command param1 param2 (shell form)

   通常是和 CMD 一起使用,请查看docker 官方介绍