Dockerfile
Dockerfile 是一个文本文件,它通过一系列指令来定义 Docker 镜像的构建过程。每一条指令都会创建一个新的镜像层,并对基础镜像进行修改。这些层的设计使得镜像构建过程既高效又可追踪。使用 Dockerfile 来创建 Docker 镜像而不是通过容器docker commit
命令来创建镜像有几个重要的原因:
- 可重复性:
Dockerfile 提供了一种可重复的方式来构建镜像。通过使用 Dockerfile,你可以确保每次构建的镜像都是一致的,这对于开发、测试和生产环境的一致性至关重要。而使用docker commit
则是基于已经运行的容器的当前状态来创建镜像,这种方式很容易受到容器历史操作和当前状态的影响,导致镜像的不一致性。 - 文档化:
Dockerfile 本身就是一种文档,它记录了构建镜像所需的所有步骤。这使得其他开发者可以很容易地理解如何构建和运行应用,而不需要额外的文档或说明。相比之下,使用docker commit
创建的镜像缺乏这种透明度,因为它不会记录构建过程中的任何步骤或决策。 - 版本控制:
Dockerfile 可以很容易地进行版本控制,因为它是一个文本文件。这意味着你可以跟踪镜像构建过程中的变更,回滚到旧版本,或者在不同的分支上进行实验。而docker commit
创建的镜像则难以进行有效的版本控制。 - 自动化和集成:
Dockerfile 可以与各种自动化工具和持续集成/持续部署(CI/CD)系统无缝集成。例如,你可以使用 Jenkins、Travis CI、GitLab CI 等工具自动构建和推送 Docker 镜像。而docker commit
则不适合这种自动化流程。 - 可维护性和可扩展性:
使用 Dockerfile 构建的镜像更容易维护和扩展。如果你需要更新应用或其依赖,只需修改 Dockerfile 中的相应指令,然后重新构建镜像即可。这种方式使得升级和维护变得更加简单和可靠。而使用docker commit
创建的镜像则可能需要手动修改容器的文件系统,这会增加维护的复杂性。 - 社区和生态系统:
Docker Hub 和其他镜像仓库上有着丰富的、由社区维护的官方和第三方镜像。这些镜像通常都是通过 Dockerfile 构建的,使得用户可以轻松地找到、使用和信任这些镜像。而docker commit
创建的镜像则不太可能被广泛地分享和使用。 - 安全性:
Dockerfile 允许开发者在构建过程中包含安全最佳实践,如最小化基础镜像、移除不必要的软件包、设置安全环境变量等。这有助于创建更加安全的镜像。而docker commit
则容易包含不必要的文件和配置,可能导致安全漏洞。
1. 编写 Dockerfile
指令 (Instructions)
Dockerfile 中的指令是构建镜像的基本单位。每一条指令都会在当前镜像的顶部创建一个新的层。这些层是只读的,并且可以被共享和重用。Dockerfile 中的一些常用指令包括:
- FROM:指定基础镜像。
- RUN:在镜像中执行命令。
- CMD:设置容器启动时默认执行的命令。
- COPY 或 ADD:从构建上下文复制文件到镜像中。
- ENV:设置环境变量。
- EXPOSE:声明容器运行时监听的端口。
- ENTRYPOINT 或 CMD:设置容器的入口点。
- VOLUME:创建数据卷,用于持久化或共享数据。
- WORKDIR:设置工作目录。
- USER:指定运行容器时的用户身份。
- MAINTAINER 或 LABEL:添加元数据信息
FROM
FROM
指令是 Dockerfile 中的第一条指令,用于指定新镜像的基础镜像。所有的其他操作都将在这个基础镜像之上进行。
FROM ubuntu:18.04
这条指令使用 ubuntu:18.04
作为新镜像的基础镜像。
RUN和CMD、ENTRYPOINT
RUN: RUN 命令执行命令并创建新的镜像层,RUN 通常用于安装软件、复制文件、设置环境变量等构建过程中需要执行的操作。如:
RUN apt-get update && apt-get install -y nginx
CMD设置容器启动后默认执行的命令及其参数。如:
#这条指令告诉容器在启动时执行 nginx 命令,并带上 -g 选项和 daemon off; 配置。
CMD ["nginx", "-g", "daemon off;"]
CMD 指令在 Dockerfile 中用于设置容器启动时默认执行的命令。CMD 指令有三种用法:
- 如果 Dockerfile 中没有 ENTRYPOINT 指令,CMD 会被用作容器的入口点,即容器启动时执行的默认命令。
- 如果 Dockerfile 中有 ENTRYPOINT 指令,CMD 会被用作 ENTRYPOINT 命令的参数。
- 在运行容器时,docker run 命令行中指定的参数会覆盖 CMD 指令。
让我们通过一些具体的例子来解释这些规则:
例子 1: 没有 ENTRYPOINT 时的 CMD
假设我们有一个 Dockerfile 如下:
复制
FROM ubuntu:18.04
CMD ["bash"]
在这个例子中,没有指定 ENTRYPOINT 指令,所以 CMD 会作为容器的入口点。当你运行这个容器时:
docker run myimage
这个命令将会启动一个 Bash shell。因为 CMD 指定了默认命令 bash,所以容器会执行这个命令。
例子 2: 有 ENTRYPOINT 时的 CMD
现在,让我们修改 Dockerfile,添加一个 ENTRYPOINT 指令:
FROM ubuntu:18.04
ENTRYPOINT ["/usr/bin/myapp"]
CMD ["start"]
在这个例子中,ENTRYPOINT 指定了一个应用程序 /usr/bin/myapp 作为容器的入口点,而 CMD 指定了 start 作为这个应用程序的参数。当你运行容器时:
docker run myimage
这个命令将会执行 /usr/bin/myapp start。即使没有在 docker run 命令中指定任何参数,容器也会执行 ENTRYPOINT 和 CMD 组合的命令。
例子 3: docker run 覆盖 CMD
如果我们在运行容器时指定了参数,这些参数将会覆盖 CMD 指令:
FROM ubuntu:18.04
ENTRYPOINT ["/usr/bin/myapp"]
CMD ["start"]
现在,我们以不同的方式运行容器:
docker run myimage arg1 arg2
在这个例子中,docker run 命令行中的 arg1 和 arg2 参数将会覆盖 CMD 指定的 start。所以容器将会执行 /usr/bin/myapp arg1 arg2。
COPY 和 ADD
COPY
和 ADD
指令用于从构建上下文(通常是 Dockerfile 所在目录)复制文件到镜像中。COPY
用于复制文件,而 ADD
除了复制文件外,还可以解压压缩文件。
- 简单复制:如果你只需要复制文件或目录,而不涉及解压缩或从 URL 下载,那么 COPY 是更简单直接的选择。
- 解压缩或下载:如果你需要在构建过程中解压缩文件或从网络下载文件,那么 ADD 是更合适的指令。
COPY示例:
#COPY <源路径> <目标路径>
#复制
COPY . /app
ADD示例:
#ADD <源路径/URL> <目标路径>
ADD https://example.com/app.tar.gz /app/
ADD archive.tar.gz /app/
ENV
ENV
指令用于设置环境变量。这些变量可以在镜像构建过程中使用,也可以在容器运行时使用。
示例:
ENV APP_ENV production
这条指令设置了名为 APP_ENV
的环境变量,并将其值设置为 production
。
EXPOSE
EXPOSE
指令用于声明容器在运行时监听的端口。这个指令不会实际打开端口,而是为运行容器时的 -p
参数提供信息。
示例:
EXPOSE 80
这条指令声明容器在运行时需要暴露 80 端口。
VOLUME
通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,是自动生成的。
VOLUME
指令用于创建数据卷,用于持久化或共享数据。数据卷是一个可以被容器访问的特殊目录,它与容器的文件系统分离,因此即使容器被删除,数据也不会丢失。
VOLUME /data
docker inspect 查看通过该dockerfile创建的镜像生成的容器,可以看到如下信息
WORKDIR
WORKDIR
指令用于设置工作目录,即容器内部的当前目录。所有 RUN、CMD、ENTRYPOINT、COPY 和 ADD 等指令都会在这个目录下执行。
WORKDIR /app
这条指令设置了容器内的工作目录为 /app
。
USER
USER
指令用于指定运行容器时的用户身份。这个指令可以提高容器的安全性,因为它限制了容器内部运行的进程的权限。
示例:
USER appuser
这条指令指定容器在运行时使用 appuser
用户身份。
MAINTAINER 或 LABEL
MAINTAINER
和 LABEL
指令用于添加元数据信息到镜像中。MAINTAINER
指定了镜像维护者的联系信息,而 LABEL
可以添加任意键值对信息。
LABEL version="1.0" description="My App"
这条指令给镜像添加了 version
和 description
两个标签。
通过这些指令,你可以创建一个完整的 Dockerfile 来定义你的镜像。每一条指令都会创建一个新的层,这些层共同构成了最终的 Docker 镜像。
完整示例:
首先,你需要创建一个文本文件,命名为 Dockerfile
(注意,文件名是区分大小写的)。在这个文件中,你将使用一系列的指令来定义你的镜像。
# 使用官方的基础镜像作为起点
FROM ubuntu:20.04
# 设置环境变量,防止在安装过程中产生交互式提示
ENV DEBIAN_FRONTEND=noninteractive
# 更新软件包列表并安装必要的软件
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
--no-install-recommends
# 设置工作目录
WORKDIR /app
# 将当前目录下的所有文件复制到容器的 /app 目录下
COPY . /app
# 使用 pip 安装 Python 依赖
RUN pip3 install --no-deps -r requirements.txt
# 声明容器运行时监听的端口
EXPOSE 8000
# 设置容器启动时执行的命令
CMD ["python3", "app.py"]
通过 Dockerfile创建镜像
创建镜像
docker build -t [名称:标签] [路径]
名称
是你为镜像指定的名称。标签
是镜像的版本号,通常是镜像的不同版本的标识符。如果不指定标签,Docker 默认使用latest
作为标签。路径
是 Dockerfile 所在的目录路径。Docker 会在这个路径下查找名为Dockerfile
的文件来构建镜像。
示例
假设你有一个名为 Dockerfile
的文件,它位于当前目录中,你想构建一个名为 myapp
的镜像,带有 1.0
标签:
docker build -t myapp:1.0 .
在这个例子中,.
表示 Docker 应该在当前目录下查找 Dockerfile
文件。构建完成后,你可以使用以下命令来查看新创建的镜像:
docker images
你会在列表中看到 myapp:1.0
镜像。
构建镜像的其他选项
除了 -t
标志,docker build
命令还支持其他选项,例如:
--no-cache
:不使用构建缓存,每次都从头开始构建镜像。--pull
:总是尝试通过拉取新的版本来更新镜像的基础层。--progress
:设置构建进度输出的格式(tty
、json
)。