在本文中,您将学习从头开始构建 Docker 映像,并使用以下命令将应用程序部署和运行为 Docker 容器:Dockerfile
如您所知,Docker 是一种用于在轻量级容器中打包、部署和运行应用程序的工具。如果您想了解 Docker 的基础知识,请参阅Docker 解释。
如果您没有安装 Docker,请查看docker 安装指南。
其他文章:
Dockerfile 解释
Docker 镜像的基本构建块是Dockerfile
Dockerfile
是一个包含指令和参数的简单文本文件。Docker 可以通过读取Dockerfile
.
在 Dockerfile 中,左边的所有内容都是指令,右边是这些指令的参数。请记住,文件名"Dockerfile"
没有任何扩展名。
下表包含重要的Dockerfile指令及其解释。
Dockerfile指令 | 解释 |
---|---|
FROM | 指定可以从容器注册表中提取的基础镜像(Docker hub、GCR、Quay、ECR 等) |
RUN | 在映像构建过程中执行命令。 |
ENV | 设置Image内的环境变量。它将在构建期间以及正在运行的容器中可用。如果您只想设置构建时变量,请使用 ARG 指令。 |
COPY | 将本地文件和目录复制到镜像中 |
EXPOSE | 指定要为 Docker 容器公开的端口。 |
ADD | 它是 COPY 指令的功能更丰富的版本。它还允许从源 URL 和自动提取的 tar 文件复制到Image中。但是,建议使用 COPY 命令而不是 ADD 命令。如果您想下载远程文件,请使用curl或使用RUN。 |
WORKDIR | 设置当前工作目录。您可以在 Dockerfile 中重复使用此指令来设置不同的工作目录。如果设置 WORKDIR,则RUN 、 CMD 、 ADD 、 COPY 、 或 等 指令ENTRYPOINT 将在该目录中执行。 |
VOLUME | 它用于创建卷或将卷挂载到 Docker 容器 |
USER | 设置运行容器时的用户名和UID。您可以使用此指令设置容器的非root用户。 |
LABEL | 用于指定Docker镜像的元数据信息 |
ARG | 用于设置带有键和值的构建时变量。当容器运行时,ARG 变量将不可用。如果您想在正在运行的容器上保留变量,请使用 ENV。 |
SHELL | 该指令用于为其后面的 RUN、CMD 和 ENTRYPOINT 指令设置 shell 选项和默认 shell。 |
CMD | 它用于在正在运行的容器中执行命令。只能有一个CMD,如果有多个CMD,则只适用于最后一个。可以从 Docker CLI覆盖它。 |
ENTRYPOINT | 指定 Docker 容器启动时将执行的命令。如果您不指定任何 ENTRYPOINT,则默认为/bin/sh -c 。您还可以使用 CLI使用该标志来覆盖 ENTRYPOINT--entrypoint 。请参阅CMD 与 ENTRYPOINT了解更多信息。 |
使用 Dockerfile 构建 Docker 镜像
在本节中,您将学习使用真实示例构建 Docker 映像。我们将从头开始创建一个带有自定义索引页面的Nginx docker 镜像。
下图显示了映像构建过程的高级工作流程。
按照下面给出的步骤构建 docker 映像。
第 1 步:创建所需的文件和文件夹
创建一个名为的文件夹nginx-image
并创建一个名为的文件夹files
mkdir nginx-image && cd nginx-image
mkdir files
创建一个 。dockerignore
文件
touch .dockerignore
步骤 2:创建示例 HTML 文件和配置文件
当您为实时项目构建 Docker 映像时,它包含代码或应用程序配置文件。
出于演示目的,我们将创建一个简单的 HTML 文件和配置文件作为我们的应用程序代码,并使用 Docker 对其进行打包。这是一个简单的 index.html
文件。如果您愿意,您可以创建自己的。
cd 进入文件夹
cd files
创建index.html
文件
vi index.html
将以下内容复制到index.html
并保存文件。
<html>
<head>
<title>Dockerfile</title>
</head>
<body>
<div class="container">
<h1>My App</h1>
<h2>This is my first app</h2>
<p>Hello everyone, This is running via Docker container</p>
</div>
</body>
</html>
创建文件名默认
vi default
将以下内容复制到默认文件中。
server {
listen 80 default_server;
listen [::]:80 default_server;
root /usr/share/nginx/html;
index index.html index.htm;
server_name _;
location / {
try_files $uri $uri/ =404;
}
}
第 3 步:选择基础镜像
我们使用FROM
其中的命令Dockerfile
指示 Docker 基于 Docker hub 或使用 Docker 配置的任何容器注册表上可用的映像创建映像。我们称之为基础镜像。
这类似于我们从虚拟机镜像在云上创建虚拟机的方式。
选择基础映像取决于我们选择的应用程序和操作系统平台。在我们的例子中,我们将选择ubuntu:18.04
基础Image。
注意:始终为您的应用程序使用官方/组织批准的基础映像,以避免潜在的漏洞。最后,我们添加了所有已验证容器基础镜像的公共注册表。另外,当涉及到生产用例时,请始终使用最小的基础映像。
第 3 步:创建 Dockerfile
在文件夹中创建 Dockerfile nginx-image
。
vi Dockerfile
Dockerfile
这是我们用例的简单内容。将内容添加到 Dockerfile。
FROM ubuntu:18.04
LABEL maintainer="contact@devopscube.com"
RUN apt-get -y update && apt-get -y install nginx
COPY files/default /etc/nginx/sites-available/default
COPY files/index.html /usr/share/nginx/html/index.html
EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
以下是每个步骤的解释:
- 根据
LABEL
指示,我们正在添加有关维护者的元数据。这不是强制性指令。 FROM
指令将从 Docker hub 拉取 Ubuntu 18.04 版本镜像。- 在第二行中,我们正在安装 Nginx。
- 然后我们将 Nginx 默认配置文件从本地
files
目录复制到目标Image目录。 - 接下来,我们将
index.html
文件从本地files
目录复制到目标Image目录中。它将覆盖Nginx 安装期间创建的默认index.html文件。 - 当 Nginx 服务侦听端口 80 时,我们会公开端口 80。
- 最后,
CMD
当 Docker 镜像启动时,我们使用指令运行 Nginx 服务器。
对于 Docker 容器,该 daemon off;
指令告诉 Nginx留在前台。这意味着 nginx 进程将继续运行,并且在您停止容器之前不会停止。它禁用 Nginx 的自我守护行为。该-g
选项指定 Nginx 的指令。
我们在前台运行该进程的原因是将控制台进程附加到标准输入、输出和错误。意思是,您可以看到来自 Nginx 进程的日志或消息
第 4 步:构建您的第一个 Docker 镜像
最终的文件夹和文件结构如下所示。
nginx-image
├── Dockerfile
└── files
├── default
└── index.html
现在,我们将使用 Docker 命令构建Image。Dockerfile
以下命令将使用同一目录构建Image。
docker build -t nginx:1.0
- -t 用于标记Image。
nginx
是Image的名称。1.0
是标签名称。如果您不添加任何标签,则默认为名为“latest”的标签。- 末尾的 (点) 表示我们将 Dockerfile 位置称为 docker 构建上下文。那是我们当前的目录。
如果它Dockerfile
位于另一个文件夹中,那么您需要明确指定它。
docker build -t nginx /path/to/folder
现在,我们可以使用此命令列出镜像。
docker images
我们可以看到标签在1.0
这里。如果我们想放置一个特定的标签,我们可以这样放置image-name:<tag>
。如果不指定任何标签,则默认为latest
标签。
docker build -t nginx:2.0 .
单个镜像可以有多个标签。我们通常采用两种方法来标记镜像:
- Stable标签– 我们可以继续提取特定标签,该标签会继续获得更新。我们的标签始终不变,但镜像内容发生了变化。
- Unique的标签——我们为每张图片使用不同且独特的标签。有多种方法可以提供唯一标签,例如日期时间戳、内部版本号、提交 ID 等。
注意:在生产环境中,推荐的 docker 镜像标记方法是语义版本控制 (Semver)。
Docker 缓存构建步骤。因此,如果我们再次构建镜像,该过程会进展得更快一些。例如,它不会再次下载 Ubuntu 18.04 映像。
使用大镜像会减慢容器的构建和部署时间。如果您想了解有关优化 Docker 镜像的更多信息,请查看reduce docker镜像指南。
第 5 步:测试 Docker 镜像
现在构建镜像后,我们将运行 Docker 镜像。命令将是
docker run -d -p 9090:80 --name webserver nginx:1.0
这里,
-d
flag 用于以分离模式运行容器-p
flag为端口号,格式为local-port:container-port--name
对于容器名称,在我们的例子中是 webserver
我们可以使用以下命令检查容器
docker ps
现在在浏览器中,如果您转到http://<host-ip>:9090
,您可以看到索引页面,其中显示我们添加到 docker 镜像的自定义 HTML 页面中的内容。
要将 Docker 镜像推送到 Docker hub,我们需要在Docker hub中创建一个帐户。
之后,执行以下命令从终端登录。它会要求输入用户名和密码。提供 Docker 中心凭据。
docker login
登录后,我们现在需要使用 docker 用户名标记我们的镜像,如下所示。
docker tag nginx:1.0 <username>/<image-name>:tag
例如,这devopscube
是 dockerhub 用户名。
docker tag nginx:1.0 devopscube/nginx:1.0
再次运行docker images
命令并检查标记的镜像是否存在。
现在我们可以使用以下命令将镜像推送到 Docker hub。
docker push devopscube/nginx:1.0
现在您可以检查此映像是否在您的 Docker Hub 帐户中可用。
将heredoc与Dockerfile结合使用
Dockerfile
还支持heredoc语法。如果我们有多个RUN
命令,那么我们可以使用heredoc
如下所示的语法。
RUN <<EOF
apt-get update
apt-get upgrade -y
apt-get install -y nginx
EOF
另外,假设您想从 Dockerfile 执行 Python 脚本,您可以使用以下语法。
RUN python3 <<EOF
with open("/hello", "w") as f:
print("Hello", file=f)
print("World", file=f)
EOF
您还可以使用heredoc语法来创建文件。这是一个 Nginx 示例。
FROM nginx
COPY <<EOF /usr/share/nginx/html/index.html
<html>
<head>
<title>Dockerfile</title>
</head>
<body>
<div class="container">
<h1>My App</h1>
<h2>This is my first app</h2>
<p>Hello everyone, This is running via Docker container</p>
</div>
</body>
</html>
EOF
Dockerfile 最佳实践
Dockerfile
我们应该遵循的一些做法:
- 使用
.dockerignore
文件排除不必要的文件和目录以提高构建的性能。 - 仅使用
受信任的基础映像
并定期更新映像。 - 中的每条指令
Dockerfile
都会向 Docker 镜像添加一个额外的层。通过合并指令来最大限度地减少层数,以提高构建的性能和时间
。 以非根用户身份
运行以避免安全漏洞。保持镜像较小
:减小镜像大小以加快部署速度,并避免在镜像中安装不必要的工具。使用最少的镜像来减少攻击面。- 对镜像使用
特定标签
而不是最新标签,以避免随着时间的推移发生重大更改。 - 避免使用多个
RUN
命令,因为它会创建多个可缓存层,这会影响构建过程的效率。 切勿共享或复制应用程序凭据或Dockerfile
. 如果您使用它,请将其添加到 .dockerignore
- 尽可能晚地使用
EXPOSE
和ENV
命令。 使用 linter
:使用 hadolint 之类的 linte检查Dockerfile 中的常见问题和最佳实践。每个容器使用一个进程
:每个容器应该运行一个进程。这使得管理和监控容器变得更加容易,并有助于保持容器的轻量级。使用多阶段构建
:使用多阶段构建来创建更小、更高效的映像。
常见的Docker构建问题
- 如果命令中存在语法错误或无效参数,
Dockerfile,
docker build
则会失败并显示错误消息。更正语法以解决此问题。 - 始终尝试使用
docker run
命令指定容器名称,否则 Docker 会自动为容器分配名称,这可能会导致一些问题。 - 有时我们会收到Bind for 0.0.0.0.:8080 failed: port is already allocate错误,这是因为某些其他软件/服务正在使用这些端口。
netstat
我们可以使用或命令检查监听端口ss
。使用不同的端口来解决此问题或停止该服务。 - 有时 Docker 无法下载软件包,并出现此错误Failed to download package 。这是因为容器可能无法访问互联网或其他依赖问题。
Docker 镜像注册表
如步骤 1 中所述,您应始终为您的应用程序选择经过验证的官方基础镜像。
下表列出了公开可用的容器注册表,您可以在其中找到经过官方验证的基础映像和应用程序映像。
Registry | 基础镜像 |
---|---|
Docker | Docker hub 基础镜像 |
Google Cloud | Distroless 基础镜像 |
AWS | ECR公共Registery |
Redhat Quay | Quay Registry |
Docker 镜像与容器
Docker 镜像是文件系统和应用程序依赖项的快照。它是一个可执行的软件包,其中包含运行应用程序所需的所有内容,例如应用程序代码、库、工具、依赖项和其他文件。您可以将其与 VM 黄金映像进行比较。
Docker 镜像以只读层的形式组织起来,相互堆叠。
Docker 容器是 Docker 镜像的运行实例。我们从 VM 映像创建 VM。类似地,我们从容器镜像创建容器。当您从 Docker 映像创建容器时,您将在现有映像层之上创建一个可写层。
Docker 镜像和容器之间的主要区别在于镜像之上的可写层。这意味着,如果您有五个容器从一个映像运行,则所有容器共享该映像中的相同只读层,并且对于所有五个容器而言,只有顶部可写层是不同的。
这意味着,当您删除容器时,可写层也会被删除。
镜像可以在没有容器的情况下存在,而容器需要镜像才能运行。我们可以从同一个镜像创建多个容器,每个容器都有自己独特的数据和状态。
Docker 镜像构建常见问题解答
如何使用 Docker hub 以外的容器注册表中的基础镜像?
默认情况下,docker 引擎配置有 docker hub 容器注册表。因此,如果您提及映像名称,它就会从 docker hub 中提取映像。但是,如果您想使用来自不同容器注册表的映像,则需要提供该映像的完整 URL。例如,从gcr.io/distroless/static-debian11
什么是 Docker 构建上下文?
Docker 构建上下文 是 docker 主机位置,在 docker 构建过程中所有代码、文件、配置和 Dockerfile 都存在于此。您可以使用点 [.] 或文件夹路径指定当前构建上下文。此外,您可以将 Dockerfile 放在与构建上下文不同的位置。作为最佳实践,构建上下文中始终只包含所需的文件。否则您将拥有不需要的文件和臃肿的 Docker 镜像。
如何从 git 存储库构建 Docker 镜像?
您可以将 docker build 命令与 git 存储库结合使用来构建 docker 映像。git 存储库应包含 Dockerfile 和成功构建映像所需的文件。
下一步是什么
同样,您可以尝试构建更多 Docker 镜像。
例如,您可以尝试对 Java 应用程序进行 Docker 化并运行它。
此外,您可以对以下应用程序进行 docker 化以获得更多知识。
- Python
- NodeJS
- Django
- FastAPI
结论
在本文中,我们讨论了如何构建 Docker 映像并使用Dockerfile
.
我们Dockerfile
详细讨论并通过了一些好的实践来编写它。
Podman 是另一个容器工具,您可以使用它来管理容器。要了解更多信息,请查看podman 教程。