相对于之前我们介绍的提交容器修改,再进行镜像迁移的方式相比,使用 Dockerfile 进行这项工作有很多优势,我总结了几项尤为突出的。

  • Dockerfile的体积远小于镜像包,更容易进行快速迁移和部署。
  • 环境构建流程记录了Dockerfile 中,能够直观的看到镜像构建的顺序和逻辑。
  • 使用Dockerfile 来构建镜像能够更轻松的实现自动部署等自动化流程。
  • 在修改环境搭建细节时,修改Dockerfile 文件要比从新提交镜像来的轻松、简单。

事实上,在实际使用中,我们也很少会选择容器提交这种方法来构建镜像,而是几乎都采用 Dockerfile 来制作镜像。所以说,学会 Dockerfile 的编写是所有熟练使用 Docker 的开发者必须掌握的能力。

纸上得来终觉浅,光说很多关于 Dockerfile 的概念其实对我们开发使用来说意义不大,这里我们直接学习如何编写一个用于构建镜像的 Dockerfile。

首先我们来看一个完整的 Dockerfile 的例子,这是用于构建 Docker 官方所提供的 Redis 镜像的 Dockerfile 文件。

  1. FROM debian:stretch-slim
  2. # add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
  3. RUN groupadd -r redis && useradd -r -g redis redis
  4. # grab gosu for easy step-down from root
  5. # https://github.com/tianon/gosu/releases
  6. ENV GOSU_VERSION 1.10
  7. RUN set -ex; \
  8. \
  9. fetchDeps=" \
  10. ca-certificates \
  11. dirmngr \
  12. gnupg \
  13. wget \
  14. "; \
  15. apt-get update; \
  16. apt-get install -y --no-install-recommends $fetchDeps; \
  17. rm -rf /var/lib/apt/lists/*; \
  18. \
  19. dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
  20. wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
  21. wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
  22. export GNUPGHOME="$(mktemp -d)"; \
  23. gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
  24. gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
  25. gpgconf --kill all; \
  26. rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc; \
  27. chmod +x /usr/local/bin/gosu; \
  28. gosu nobody true; \
  29. \
  30. apt-get purge -y --auto-remove $fetchDeps
  31. ENV REDIS_VERSION 3.2.12
  32. ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-3.2.12.tar.gz
  33. ENV REDIS_DOWNLOAD_SHA 98c4254ae1be4e452aa7884245471501c9aa657993e0318d88f048093e7f88fd
  34. # for redis-sentinel see: http://redis.io/topics/sentinel
  35. RUN set -ex; \
  36. \
  37. buildDeps=' \
  38. wget \
  39. \
  40. gcc \
  41. libc6-dev \
  42. make \
  43. '; \
  44. apt-get update; \
  45. apt-get install -y $buildDeps --no-install-recommends; \
  46. rm -rf /var/lib/apt/lists/*; \
  47. \
  48. wget -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \
  49. echo "$REDIS_DOWNLOAD_SHA *redis.tar.gz" | sha256sum -c -; \
  50. mkdir -p /usr/src/redis; \
  51. tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \
  52. rm redis.tar.gz; \
  53. \
  54. # disable Redis protected mode [1] as it is unnecessary in context of Docker
  55. # (ports are not automatically exposed when running inside Docker, but rather explicitly by specifying -p / -P)
  56. # [1]: https://github.com/antirez/redis/commit/edd4d555df57dc84265fdfb4ef59a4678832f6da
  57. grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 1$' /usr/src/redis/src/server.h; \
  58. sed -ri 's!^(#define CONFIG_DEFAULT_PROTECTED_MODE) 1$!\1 0!' /usr/src/redis/src/server.h; \
  59. grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 0$' /usr/src/redis/src/server.h; \
  60. # for future reference, we modify this directly in the source instead of just supplying a default configuration flag because apparently "if you specify any argument to redis-server, [it assumes] you are going to specify everything"
  61. # see also https://github.com/docker-library/redis/issues/4#issuecomment-50780840
  62. # (more exactly, this makes sure the default behavior of "save on SIGTERM" stays functional by default)
  63. \
  64. make -C /usr/src/redis -j "$(nproc)"; \
  65. make -C /usr/src/redis install; \
  66. \
  67. rm -r /usr/src/redis; \
  68. \
  69. apt-get purge -y --auto-remove $buildDeps
  70. RUN mkdir /data && chown redis:redis /data
  71. VOLUME /data
  72. WORKDIR /data
  73. COPY docker-entrypoint.sh /usr/local/bin/
  74. ENTRYPOINT ["docker-entrypoint.sh"]
  75. EXPOSE 6379
  76. CMD ["redis-server"]

其中可以很明确的见到我们之前说的 Dockerfile 文件的两种行结构,也就是指令行和注释行,接下来我们着重关注指令行,因为这是构建镜像的关键。

Dockerfile 的结构

总体上来说,我们可以将 Dockerfile 理解为一个由上往下执行指令的脚本文件。当我们调用构建命令让 Docker 通过我们给出的 Dockerfile 构建镜像时,Docker 会逐一按顺序解析 Dockerfile 中的指令,并根据它们不同的含义执行不同的操作。
如果进行细分,我们可以将 Dockerfile 的指令简单分为五大类。

  • 基础指令:用于定义新镜像的基础和性质。
  • 控制指令:是指导镜像构建的核心部分,用于描述镜像在构建过程中需要执行的命令。
  • 引入指令:用于将外部文件直接引入到构建镜像内部。
  • 执行指令:能够为基于镜像所创建的容器,指定在启动时需要执行的脚本或命令。
  • 配置指令:对镜像以及基于镜像所创建的容器,可以通过配置指令对其网络、用户等内容进行配置。

这五类命令并非都会出现在一个 Dockerfile 里,但却对基于这个 Dockerfile 所构建镜像形成不同的影响。