1 Dockerfile的指令

Docker 可以通过 Dockerfile 的内容来自动构建镜像。
Dockerfile 是一个包含创建镜像所有命令的文本文件,通过docker build命令可以根据 Dockerfile 的内容构建镜像,在介绍如何构建之前先介绍下 Dockerfile 的基本语法结构。
Dockerfile 有以下指令选项:

  1. FROM
  2. MAINTAINER
  3. RUN
  4. CMD
  5. EXPOSE
  6. ENV
  7. ADD
  8. COPY
  9. ENTRYPOINT
  10. VOLUME
  11. USER
  12. WORKDIR
  13. ONBUILD

1.1 FROM

用法:
FROM 02 Dockerfile 指令 - 图1
或者
FROM 02 Dockerfile 指令 - 图2
FROM指定构建镜像的基础源镜像,如果本地没有指定的镜像,则会自动从 Docker 的公共库 pull 镜像下来。
FROM必须是 Dockerfile 中非注释行的第一个指令,即一个 Dockerfile 从FROM语句开始。
FROM可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像。
如果FROM语句没有指定镜像标签,则默认使用latest标签。

1.2 MAINTAINER

用法:
MAINTAINER
指定创建镜像的用户

1.3 RUN

RUN 有两种使用方式
RUN
RUN “executable”, “param1”, “param2”
每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像,后续的RUN都在之前RUN提交后的镜像为基础,镜像是分层的,可以通过一个镜像的任何一个历史提交点来创建,类似源码的 版本控制 。
exec 方式会被解析为一个 JSON 数组,所以必须使用双引号而不是单引号。exec 方式不会调用一个命令 shell,所以也就不会继承相应的变量,如:
RUN [ “echo”, “$HOME” ]
这种方式是不会达到输出 HOME 变量的,正确的方式应该是这样的
RUN [ “sh”, “-c”, “echo”, “$HOME” ]
RUN产生的缓存在下一次构建的时候是不会失效的,会被重用,可以使用—no-cache选项,即docker build —no-cache,如此便不会缓存。

1.4 CMD

CMD有三种使用方式:
CMD “executable”,”param1”,”param2”
CMD “param1”,”param2”
CMD command param1 param2 (shell form)
CMD指定在 Dockerfile 中只能使用一次,如果有多个,则只有最后一个会生效。
CMD的目的是为了在启动容器时提供一个默认的命令执行选项。如果用户启动容器时指定了运行的命令,则会覆盖掉CMD指定的命令。
CMD会在启动容器的时候执行,build 时不执行,而RUN只是在构建镜像的时候执行,后续镜像构建完成之后,启动容器就与RUN无关了,这个初学者容易弄混这个概念,这里简单注解一下。

1.5 EXPOSE

EXPOSE […]
告诉 Docker 服务端容器对外映射的本地端口,需要在 docker run 的时候使用-p或者-P选项生效。

1.6 ENV

ENV # 只能设置一个变量
ENV = … # 允许一次设置多个变量
指定一个环节变量,会被后续RUN指令使用,并在容器运行时保留。
例子:
ENV myName=”John Doe” myDog=Rex\ The\ Dog \
myCat=fluffy
等同于
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

1.7 ADD

ADD
ADD复制本地主机文件、目录或者远程文件 URLS 从 并且添加到容器指定路径中 。
支持通过 Go 的正则模糊匹配,具体规则可参见 Go filepath.Match
ADD hom* /mydir/ # adds all files starting with “hom”
ADD hom?.txt /mydir/ # ? is replaced with any single character
路径必须是绝对路径,如果 不存在,会自动创建对应目录
路径必须是 Dockerfile 所在路径的相对路径
如果是一个目录,只会复制目录下的内容,而目录本身则不会被复制

1.8 COPY

COPY
COPY复制新文件或者目录从 并且添加到容器指定路径中 。用法同ADD,唯一的不同是不能指定远程文件 URLS。

1.9 ENTRYPOINT

ENTRYPOINT “executable”, “param1”, “param2”
ENTRYPOINT command param1 param2 (shell form)
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖,而CMD是可以被覆盖的。如果需要覆盖,则可以使用docker run —entrypoint选项。
每个 Dockerfile 中只能有一个ENTRYPOINT,当指定多个时,只有最后一个生效。
Exec form ENTRYPOINT 例子
通过ENTRYPOINT使用 exec form 方式设置稳定的默认命令和选项,而使用CMD添加默认之外经常被改动的选项。
FROM ubuntu
ENTRYPOINT [“top”, “-b”]
CMD [“-c”]
通过 Dockerfile 使用ENTRYPOINT展示前台运行 Apache 服务

  1. FROM debian:stable
  2. RUN apt-get update && apt-get install -y --force-yes apache2
  3. EXPOSE 80 443
  4. VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
  5. ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

Shell form ENTRYPOINT 例子
这种方式会在/bin/sh -c中执行,会忽略任何CMD或者docker run命令行选项,为了确保docker stop能够停止长时间运行ENTRYPOINT的容器,确保执行的时候使用exec选项。
FROM ubuntu
ENTRYPOINT exec top -b
如果在ENTRYPOINT忘记使用exec选项,则可以使用CMD补上:
FROM ubuntu
ENTRYPOINT top -b
CMD —ignored-param1 # —ignored-param2 … —ignored-param3 … 依此类推

1.10 VOLUME

VOLUME [“/data”]
创建一个可以从本地主机或其他容器挂载的挂载点,后续具体介绍。

1.11 USER

USER daemon
指定运行容器时的用户名或 UID,后续的RUN、CMD、ENTRYPOINT也会使用指定用户。

1.12 WORKDIR

WORKDIR /path/to/workdir
为后续的RUN、CMD、ENTRYPOINT指令配置工作目录。可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。

  1. WORKDIR /a
  2. WORKDIR b
  3. WORKDIR c
  4. RUN pwd

最终路径是/a/b/c。
WORKDIR指令可以在ENV设置变量之后调用环境变量:
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
最终路径则为 /path/$DIRNAME。

1.13 ONBUILD

ONBUILD [INSTRUCTION]
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,Dockerfile 使用如下的内容创建了镜像 image-A:
[…]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build —dir /app/src
[…]
如果基于 image-A 创建新的镜像时,新的 Dockerfile 中使用 FROM image-A 指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。

  1. # Automatically run the following
  2. ADD . /app/src
  3. RUN /usr/local/bin/python-build --dir /app/src

使用ONBUILD指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。

1.14 Dockerfile Examples

  1. # Nginx
  2. # VERSION 0.0.1
  3. FROM ubuntu
  4. MAINTAINER Victor Vieux <victor@docker.com>
  5. RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
  6. # Firefox over VNC
  7. # VERSION 0.3
  8. FROM ubuntu
  9. # Install vnc, xvfb in order to create a 'fake' display and firefox
  10. RUN apt-get update && apt-get install -y x11vnc xvfb firefox
  11. RUN mkdir ~/.vnc
  12. # Setup a password
  13. RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
  14. # Autostart firefox (might not be the best way, but it does the trick)
  15. RUN bash -c 'echo "firefox" >> /.bashrc'
  16. EXPOSE 5900
  17. CMD ["x11vnc", "-forever", "-usepw", "-create"]
  18. # Multiple images example
  19. # VERSION 0.1
  20. FROM ubuntu
  21. RUN echo foo > bar
  22. # Will output something like ===> 907ad6c2736f
  23. FROM ubuntu
  24. RUN echo moo > oink
  25. # Will output something like ===> 695d7793cbe4
  26. # You᾿ll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
  27. # /oink.

2 docker build创建镜像

  1. $ docker build --help
  2. Usage: docker build [OPTIONS] PATH | URL | -
  3. Build a new image from the source code at PATH
  4. --force-rm=false Always remove intermediate containers, even after unsuccessful builds # 移除过渡容器,即使构建失败
  5. --no-cache=false Do not use cache when building the image # 不实用 cache
  6. -q, --quiet=false Suppress the verbose output generated by the containers
  7. --rm=true Remove intermediate containers after a successful build # 构建成功后移除过渡层容器
  8. -t, --tag="" Repository name (and optionally a tag) to be applied to the resulting image in case of success

参考文档: Dockerfile Reference

3 dockerfile 最佳实践

3.1 使用.dockerignore文件

为了在docker build过程中更快上传和更加高效,应该使用一个.dockerignore文件用来排除构建镜像时不需要的文件或目录。例如,除非. Git 在构建过程中需要用到,否则你应该将它添加到.dockerignore文件中,这样可以节省很多时间。

3.2 最佳实践

所谓最佳实践,实际上是从需求出发, 来定制适合自己、高效方便的镜像。
首先,要尽量吃透每个指令的含义和执行效果,自己多编写一些简单的例子进行测试,弄清楚了再横写正式的Dockerfile。此外,Docker Hub官方仓库中提供了大量的优秀镜像和对应的Dockefile,可以通过阅读它们来学习如何撰写高效的Dockerfile。
建议在生成镜像过程中,尝试从如下角度进行思考,完善所生成的镜像。

  • 精简镜像用途。尽量让每个镜像的用途都比较集中、单一, 避免构造大而复杂、多功能的镜像;
  • 选用合适的基础镜像。过大的基础镜像会造成生成臃肿的镜像,一般推荐较为小巧的debian镜像;
  • 提供足够清晰的命合注释和维护者信息。 Dockerfile也是一 种代码,需要考虑方便后续扩展和他人使用;
  • 正确使用版本号:使用明确的版本号信息。如1.0. 2.0. 而非latest.将避免内容不一致可能引发的惨案;
  • 减少镜像层数。如果希望所生成镜像的层数尽量少,则要尽量合并指令,例如多个RUN指令可以合并为一条;
  • 及时删除临时文件和缓存文件。特别是在执行apt-get指命后,/var/cache/apt下面会缓存些安装包 ;
  • 提高生成速度。如合理使用缓存,减少内容目录下的文件,或使用docker ignore文件指定等;
  • 调整合理的令顺序。在开启缓存的情况下,内容不变的指令尽量放在前面,这样可以尽量复用;
  • 减少外部源的干扰。如果确实要从外部引入数据,需要指定持久的地止,并带有版本信息,让他人可以重复而不出错。

    3.3 Dockerfile 指令

    FROM: 只要可能就使用官方镜像库作为基础镜像
    RUN: 为保持可读性、方便理解、可维护性,把长或者复杂的RUN语句使用\分隔符分成多行
    不建议RUN apt-get update独立成行,否则如果后续包有更新,那么也不会再执行更新
    避免使用RUN apt-get upgrade或dist-upgrade,很多必要的包在一个非privileged权限的容器里是无法升级的。如果知道某个包更新,使用apt-get install -y xxx
    标准写法
    RUN apt-get update && apt-get install -y package-bar package-foo
    例子:
    1. RUN apt-get update && apt-get install -y \
    2. aufs-tools \
    3. automake \
    4. btrfs-tools \
    5. build-essential \
    6. curl \
    7. dpkg-sig \
    8. git \
    9. iptables \
    10. libapparmor-dev \
    11. libcap-dev \
    12. libsqlite3-dev \
    13. lxc=1.0* \
    14. mercurial \
    15. parallel \
    16. reprepro \
    17. ruby1.9.1 \
    18. ruby1.9.1-dev \
    19. s3cmd=1.1.0*
    CMD: 推荐使用CMD [“executable”, “param1”, “param2”…]这种格式,CMD [“param”, “param”]则配合ENTRYPOINT使用
    EXPOSE: Dockerfile 指定要公开的端口,使用docker run时指定映射到宿主机的端口即可
    ENV: 为了使新的软件更容易运行,可以使用ENV更新PATH变量。如ENV PATH /usr/local/nginx/bin:$PATH确保CMD [“nginx”]即可运行
    ENV也可以这样定义变量:
    1. ENV PG_MAJOR 9.3
    2. ENV PG_VERSION 9.3.4
    3. RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress &&
    4. ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
    ADDorCOPY:ADD比COPY多一些特性「tar 文件自动解包和支持远程 URL」,不推荐添加远程 URL
    如不推荐这种方式:
    1. ADD http://example.com/big.tar.xz /usr/src/things/
    2. RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
    3. RUN make -C /usr/src/things all
    推荐使用 curl 或者 wget 替换,使用如下方式:
    1. RUN mkdir -p /usr/src/things \
    2. && curl -SL http://example.com/big.tar.gz \
    3. | tar -xJC /usr/src/things \
    4. && make -C /usr/src/things all
    如果不需要添加 tar 文件,推荐使用COPY。