【Docker-Day 6】从零到一:精通 Dockerfile 核心指令 (FROM, WORKDIR, COPY, RUN)
Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来
Python系列文章目录
PyTorch系列文章目录
机器学习系列文章目录
深度学习系列文章目录
Java系列文章目录
JavaScript系列文章目录
Python系列文章目录
Go语言系列文章目录
Docker系列文章目录
01-【Docker-Day 1】告别部署噩梦:为什么说 Docker 是每个开发者的必备技能?
02-【Docker-Day 2】从零开始:手把手教你在 Windows、macOS 和 Linux 上安装 Docker
03-【Docker-Day 3】深入浅出:彻底搞懂 Docker 的三大核心基石——镜像、容器与仓库
04-【Docker-Day 4】从创建到删除:一文精通 Docker 容器核心操作命令
05-【Docker-Day 5】玩转 Docker 镜像:search, pull, tag, rmi 四大金刚命令详解
06-【Docker-Day 6】从零到一:精通 Dockerfile 核心指令 (FROM, WORKDIR, COPY, RUN)
文章目录
- Langchain系列文章目录
- Python系列文章目录
- PyTorch系列文章目录
- 机器学习系列文章目录
- 深度学习系列文章目录
- Java系列文章目录
- JavaScript系列文章目录
- Python系列文章目录
- Go语言系列文章目录
- Docker系列文章目录
- 摘要
- 一、为什么需要 Dockerfile?
- 二、Dockerfile 的基本结构与执行机制
- 2.1 文件结构与基本语法
- 2.2 构建上下文 (Build Context)
- 2.3 分层构建 (Layered Build)
- 三、核心指令详解(上)
- 3.1 `FROM`:指定基础镜像
- (1) 语法
- (2) 示例
- 3.2 `WORKDIR`:设定工作目录
- (1) 语法
- (2) 作用与特性
- (3) 示例
- 3.3 `COPY`:复制文件或目录
- (1) 语法
- (2) 关键点
- (3) 示例
- 3.4 `RUN`:执行命令
- (1) 两种形式
- (2) 最佳实践:合并 `RUN` 指令
- 四、实战:构建一个自定义 Nginx 镜像
- 4.1 准备工作
- (1) 创建项目目录
- (2) 创建自定义网页
- (3) 编写 Dockerfile
- 4.2 构建镜像
- 4.3 运行并验证
- (1) 查看新镜像
- (2) 运行容器
- (3) 验证结果
- 五、总结
摘要
在前面的学习中,我们已经掌握了如何从 Docker Hub 拉取并运行现成的镜像。然而,在真实的项目中,我们往往需要根据自己的应用需求,创建专属的镜像。本文将深入探讨 Docker 中实现镜像构建自动化的核心工具——Dockerfile
。我们将从其基本概念与结构入手,详细解析 FROM
, WORKDIR
, COPY
, RUN
这四个最基础也最重要的指令,并通过一个完整的实战案例,手把手教你如何编写第一个 Dockerfile,并使用 docker build
命令构建出一个自定义的 Web 服务器镜像。
一、为什么需要 Dockerfile?
在我们深入指令之前,首先要理解 Dockerfile 扮演的角色。
想象一下,你新加入一个项目组,需要配置开发环境。通常,你会拿到一份长长的环境搭建文档,上面写着:“第一步,安装 Ubuntu 20.04;第二步,安装 Python 3.8;第三步,安装 Nginx;第四步,修改 Nginx 配置文件…”。这个过程繁琐、耗时且容易出错。
Dockerfile 就是这份“环境搭建文档”的代码化、自动化版本。它是一个包含了一系列指令的文本文件,这些指令按顺序描述了如何从一个基础镜像开始,一步步地安装依赖、复制文件、配置环境,最终构建出一个全新的、符合我们需求的 Docker 镜像。
使用 Dockerfile 的核心优势:
- 透明化与可追溯: Dockerfile 本身就是镜像构建过程的完整记录,任何人都可以通过阅读它来理解镜像的构成。
- 自动化与一致性: 只需一条
docker build
命令,即可在任何安装了 Docker 的机器上重现一模一样的环境,彻底告别“在我机器上是好的”这一魔咒。 - 版本控制: 我们可以像管理项目代码一样,将 Dockerfile 纳入 Git 等版本控制系统,轻松追踪环境的每一次变更。
二、Dockerfile 的基本结构与执行机制
2.1 文件结构与基本语法
一个 Dockerfile 通常由一系列的“指令+参数”组成,其基本语法非常直观:
# 这是一个注释 (Comment)
INSTRUCTION arguments
- #: 行首的
#
表示注释。 - INSTRUCTION: 指令不区分大小写,但官方推荐使用大写,以便与参数区分。
- arguments: 指令的参数。
Docker 引擎会从上到下,逐行解析并执行 Dockerfile 中的指令。
2.2 构建上下文 (Build Context)
执行 docker build
命令时,我们所在的目录被称为构建上下文 (Build Context)。Docker 客户端会将这个目录下的所有文件(可以被 .dockerignore
排除)打包发送给 Docker 守护进程(Docker Daemon)。这样,在 Dockerfile 中使用 COPY
或 ADD
指令时,Docker 才能找到我们想要复制到镜像中的本地文件。
2.3 分层构建 (Layered Build)
Docker 镜像最核心的概念之一就是分层。Dockerfile 中的每一条可执行指令(如 RUN
, COPY
, ADD
)都会创建一个新的镜像层 (Image Layer)。
分层构建的好处:
- 缓存复用 (Cache Reuse): 这是 Docker 构建如此高效的关键。如果 Dockerfile 的某一行指令没有发生变化,并且它所依赖的上一层也没有变化,那么 Docker 会直接使用之前构建时生成的缓存层,而不是重新执行该指令。这极大地加快了镜像的重复构建速度。
- 资源共享: 多个镜像可以共享相同的底层。例如,你有十个基于
ubuntu:20.04
构建的应用镜像,它们在物理机上只会存储一份ubuntu:20.04
的基础层。
三、核心指令详解(上)
下面我们来正式学习构建镜像的四大基础指令。
3.1 FROM
:指定基础镜像
FROM
指令是 Dockerfile 的基石,它必须是 Dockerfile 中的第一条非注释指令。它告诉 Docker 我们要构建的新镜像是基于哪个镜像开始的。
(1) 语法
FROM <image>
FROM <image>:<tag>
FROM <image>@<digest>
<image>
: 基础镜像的名称,例如ubuntu
。<tag>
: 镜像的标签(版本),例如20.04
。如果省略,默认为latest
。<digest>
: 镜像的唯一内容摘要哈希值。使用摘要可以保证你使用的镜像是绝对确定的,不会因标签被覆盖而改变。
(2) 示例
如果我们想构建一个基于轻量级 Alpine Linux 的 Nginx 服务器,Dockerfile 的开头将是:
# 从 Docker Hub 拉取 alpine 标签的 nginx 镜像作为基础
FROM nginx:alpine
选择一个合适的基础镜像是优化的第一步。例如,nginx:alpine
(约 23MB)就比 nginx:latest
(基于 Debian,约 142MB)小得多。
3.2 WORKDIR
:设定工作目录
WORKDIR
指令用于为 Dockerfile 中在其之后执行的任何 RUN
, CMD
, ENTRYPOINT
, COPY
, ADD
指令设置工作目录。
(1) 语法
WORKDIR /path/to/workdir
(2) 作用与特性
- 简化路径:避免在后续指令中不断重复书写长路径。
- 自动创建:如果指定的目录不存在,Docker 会自动创建它。
- 可多次使用:可以多次使用
WORKDIR
来切换当前目录。路径可以是相对路径,它会相对于前一个WORKDIR
指令的路径。
(3) 示例
# 不推荐的写法
RUN mkdir /app
COPY config.json /app/config.json
RUN /app/my-script.sh# 推荐的写法
WORKDIR /app
COPY config.json .
RUN ./my-script.sh
在推荐的写法中,COPY . .
和 RUN ./my-script.sh
中的 .
和 ./
都是相对于 /app
目录的,代码更清晰简洁。
3.3 COPY
:复制文件或目录
COPY
指令用于将构建上下文中的文件或目录复制到镜像内的文件系统中。
(1) 语法
COPY [--chown=<user>:<group>] <src>... <dest>
<src>
: 源文件或目录,路径是相对于构建上下文的。支持通配符,例如*.html
。<dest>
: 目标路径,在镜像内的绝对路径,或者是相对于WORKDIR
的相对路径。--chown
: (可选) 改变复制到镜像中文件的用户和用户组。
(2) 关键点
- 路径规则:源路径必须在构建上下文中。你不能
COPY ../somefile /app
。 - 目录复制:如果源是目录,它会复制目录中的所有内容(包括元数据)。
- 目标路径:如果目标路径以
/
结尾,则源会被复制到该目录下。如果目标路径不存在,Docker 会自动创建它以及所有必需的父目录。
(3) 示例
假设我们的项目目录结构如下:
.
├── Dockerfile
├── html
│ └── index.html
└── conf└── default.conf
Dockerfile 内容:
# ...
WORKDIR /usr/share/nginx/html
# 将构建上下文中的 html/index.html 复制到镜像的 /usr/share/nginx/html/index.html
COPY html/index.html .WORKDIR /etc/nginx/conf.d
# 将构建上下文中的 conf/default.conf 复制到镜像的 /etc/nginx/conf.d/default.conf
COPY conf/default.conf .
3.4 RUN
:执行命令
RUN
指令用于在镜像构建过程中执行命令。这通常用于安装软件包、创建目录、编译代码等。
(1) 两种形式
-
Shell 形式:
RUN <command>
- 命令在 shell 中执行,在 Linux 上默认是
/bin/sh -c <command>
。 - 可以直接使用 shell 语法,如环境变量替换 (
$HOME
)。 - 示例:
RUN apt-get update && apt-get install -y vim
- 命令在 shell 中执行,在 Linux 上默认是
-
Exec 形式:
RUN ["executable", "param1", "param2"]
- 命令直接由内核执行,不经过 shell 解析。
- 更适合不希望被 shell 字符串插值影响的命令。
- 参数必须是 JSON 数组格式。
- 示例:
RUN ["/bin/bash", "-c", "echo hello"]
(2) 最佳实践:合并 RUN
指令
由于每一条 RUN
指令都会创建新的一层,为了减少镜像层数、缩小镜像体积,应将多个相关的命令用 &&
连接起来,合并成一条 RUN
指令。
# 不推荐:创建了两个镜像层
RUN apt-get update
RUN apt-get install -y curl# 推荐:只创建一个镜像层
RUN apt-get update && apt-get install -y curl
四、实战:构建一个自定义 Nginx 镜像
现在,我们将运用所学知识,创建一个替换了默认欢迎页面的 Nginx 镜像。
4.1 准备工作
(1) 创建项目目录
在你的电脑上创建一个名为 my-nginx-app
的文件夹。
mkdir my-nginx-app
cd my-nginx-app
(2) 创建自定义网页
在 my-nginx-app
目录下,创建一个 index.html
文件,并写入以下内容:
<!DOCTYPE html>
<html>
<head><title>Welcome to My Custom Nginx!</title>
</head>
<body><h1>Hello from My First Dockerfile!</h1><p>This page is served by a custom Nginx image built by me.</p>
</body>
</html>
(3) 编写 Dockerfile
在 my-nginx-app
目录下,创建一个名为 Dockerfile
的文件(注意没有文件后缀),并写入以下内容:
# 步骤 1: 指定基础镜像
# 我们选择 Alpine Linux 版本的 Nginx,因为它非常小巧
FROM nginx:1.21-alpine# 步骤 2: 设置工作目录 (可选,但推荐)
# 这里我们将工作目录设置为 Nginx 默认存放网页文件的目录
WORKDIR /usr/share/nginx/html# 步骤 3: 复制文件
# 将构建上下文(当前目录)中的 index.html 文件
# 复制到镜像的当前工作目录(/usr/share/nginx/html)下
# 注意:会覆盖掉基础镜像中原有的 index.html
COPY index.html .# RUN 指令在这里不是必需的,因为我们只是替换静态文件
# 如果需要安装软件,才会用到 RUN,例如:
# RUN apk add --no-cache curl
此时,你的目录结构应该是:
my-nginx-app/
├── Dockerfile
└── index.html
4.2 构建镜像
打开终端,确保当前路径在 my-nginx-app
目录下,然后执行 docker build
命令:
# -t 参数用于给镜像打上标签,格式为 <name>:<tag>
# . 表示将当前目录作为构建上下文
docker build -t my-custom-nginx:1.0 .
你会看到类似以下的输出:
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM nginx:1.21-alpine---> a355830213b3
Step 2/3 : WORKDIR /usr/share/nginx/html---> Running in c8e2b1e3d4a5
Removing intermediate container c8e2b1e3d4a5---> 7d2f9a65b8f1
Step 3/3 : COPY index.html .---> 1a9b8f7c6e5d
Successfully built 1a9b8f7c6e5d
Successfully tagged my-custom-nginx:1.0
4.3 运行并验证
(1) 查看新镜像
使用 docker images
命令,你应该能看到我们刚刚创建的镜像:
REPOSITORY TAG IMAGE ID CREATED SIZE
my-custom-nginx 1.0 1a9b8f7c6e5d 5 seconds ago 22.8MB
(2) 运行容器
使用 docker run
命令来运行这个新镜像:
# -d: 后台运行容器
# -p 8080:80: 将主机的 8080 端口映射到容器的 80 端口
# --name: 给容器取一个名字,方便管理
docker run -d -p 8080:80 --name my-nginx-server my-custom-nginx:1.0
(3) 验证结果
打开你的浏览器,访问 http://localhost:8080
。如果一切顺利,你将看到我们自定义的 index.html
页面内容!
五、总结
本文作为 Dockerfile 的入门篇,详细介绍了其作为镜像构建蓝图的核心地位,并深入剖析了分层构建机制及其带来的缓存优势。通过本章学习,我们应掌握以下核心知识点:
- Dockerfile 的作用:它是实现环境配置代码化、构建过程自动化的关键,确保了环境的一致性与可移植性。
- 构建上下文与分层:理解了
docker build
时发送给守护进程的内容,以及每一条指令如何创建新镜像层的概念,这是理解 Docker 镜像构建效率和大小的关键。 - 四大核心指令:
FROM
:必须是第一条指令,用于定义所有构建步骤的基础。WORKDIR
:用于设定后续指令的工作目录,能让 Dockerfile 更清晰、易于维护。COPY
:将本地文件复制到镜像中的标准方式,是向镜像中添加应用代码和配置的主要手段。RUN
:在镜像构建时执行命令,主要用于安装系统依赖和软件包。
docker build
命令:掌握了使用-t
参数为镜像打标签和指定构建上下文的基本用法,并成功构建了第一个自定义镜像。
在下一篇文章中,我们将继续深入 Dockerfile 的学习,探讨如何通过 CMD
、ENTRYPOINT
指令让镜像真正“跑起来”,以及如何使用 ENV
、ARG
等指令来传递变量和参数,敬请期待。