简单说,Dockerfile 就像是一份“菜谱”——你写下要放什么原料、先做什么后做什么,Docker 就照着这份菜谱,自动给你炒出一盘一模一样的菜(也就是一个容器)。


先讲个故事:为什么要有 Dockerfile?

想象一下,你是个程序员,写了一个很酷的网站。你在自己的电脑上调试好了,一切完美。然后你想把网站部署到服务器上,让全世界都能访问。

问题来了:你的电脑是 Windows,服务器是 Linux。你的电脑上装了 Python 3.9,服务器上是 Python 3.6。你的电脑上有个叫“libssl”的库(一个加密工具包),服务器上没有。你的代码里用了个叫“requests”的库(用来发网络请求),服务器上也没装。

结果呢?你的网站一上服务器就崩溃了。你开始疯狂排查:“我电脑上明明能跑啊!”

这就是著名的“在我电脑上能跑”魔咒。

Docker 的发明就是为了打破这个魔咒。它的思路很简单:把整个运行环境(包括操作系统、依赖库、配置文件、代码本身)打包成一个“集装箱”,这个集装箱在任何地方都能一模一样地运行。

那么问题来了:怎么告诉 Docker 这个集装箱里应该装什么?答案就是 Dockerfile


Dockerfile 是什么?—— 菜谱的比喻

Dockerfile 就是一份写给 Docker 看的“菜谱”。

  • 菜谱上写:“准备一个锅,放油,加热到七成,放入葱花爆香……”
  • Dockerfile 上写:“基于 Ubuntu 系统,安装 Python,复制代码文件,设置启动命令……”

Docker 拿到这份菜谱后,会一步一步执行,最终生成一个 镜像(Image)——你可以把镜像理解成“做好的菜”,而容器(Container)就是“正在被吃的那盘菜”。

关键区别

  • 菜谱(Dockerfile)→ 可重复,可分享
  • 做好的菜(镜像)→ 可存储,可传输
  • 正在吃的菜(容器)→ 正在运行的应用实例

一个真实的 Dockerfile 长什么样?

让我们看一个最简单的例子。假设你写了一个 Python 的 Web 应用,叫 app.py,它需要 Flask(一个 Python 的 Web 框架)才能运行。

你的 Dockerfile 可能是这样的:

# 第1步:选一个基础环境
FROM python:3.9-slim

# 第2步:在容器里创建一个工作目录
WORKDIR /app

# 第3步:把本地的代码复制到容器里
COPY app.py /app/

# 第4步:安装依赖
RUN pip install flask

# 第5步:告诉容器启动时运行什么命令
CMD ["python", "app.py"]

就这么 5 行,就定义了一个完整的应用环境。


逐行拆解:每行指令在干什么?

1. FROM —— 选一口锅

类比:你要做番茄炒蛋,得先选一口锅。你是用铁锅、不粘锅,还是砂锅?FROM 就是选锅。

FROM python:3.9-slim 的意思是:基于一个已经装好 Python 3.9 的轻量级 Linux 系统来构建。这个“基础镜像”是 Docker Hub(Docker 的官方应用商店)上别人已经做好的。

为什么要有这一步? 因为你不需要从零开始装 Linux,再装 Python。直接拿别人做好的“半成品”来用,省时省力。

2. WORKDIR —— 选一个操作台

类比:你进了厨房,得先选一个台面来切菜。WORKDIR 就是告诉 Docker:“所有后续操作都在这个文件夹里进行。”

WORKDIR /app 相当于在容器里创建了一个 /app 文件夹,然后所有接下来的命令(复制、安装、运行)都在这个文件夹里执行。

为什么重要? 如果没有这一行,文件会被散乱地放在根目录下,就像把菜刀、砧板、调料全堆在地上,乱成一团。

3. COPY —— 把食材放进厨房

类比:你从冰箱里拿出鸡蛋和番茄,放到操作台上。COPY 就是把本地的文件复制到容器里。

COPY app.py /app/ 的意思是:把当前目录下的 app.py 文件,复制到容器里的 /app/ 文件夹下。

注意:这里复制的是代码文件。如果你的应用需要配置文件、图片、模板文件,都可以用 COPY 复制进去。

4. RUN —— 在厨房里做准备工作

类比:你切好番茄、打散鸡蛋,这些准备工作是在厨房里完成的。RUN 就是在容器里执行一些命令,用来准备环境。

RUN pip install flask 的意思是:在容器里运行 pip install flask 这个命令,安装 Flask 框架。

为什么不用 COPY 来装 Flask? 因为 Flask 是一个软件包,不是文件。你需要运行安装命令才能把它装进系统里。RUN 就是用来执行这类命令的。

5. CMD —— 告诉厨师最后一步做什么

类比:所有准备工作都做好了,最后一步是“开火炒菜”。CMD 就是告诉 Docker:“当容器启动时,运行这个命令。”

CMD ["python", "app.py"] 的意思是:启动容器时,执行 python app.py 来运行你的 Web 应用。

关键点CMD 只会执行一次,而且是在容器启动时执行。它定义了容器的“主进程”——如果这个进程停止了,容器也就停止了。


为什么 Dockerfile 这么重要?—— 三个核心价值

1. 可重复性(Reproducibility)

场景:你写了一个 Dockerfile,把它提交到 Git 仓库里。半年后,你的同事(或者未来的你)把这个仓库 clone 下来,执行 docker build -t myapp .,就能生成一个和当初一模一样的镜像。

没有 Dockerfile 会怎样? 你只能靠记忆去装依赖:“我记得当时装的是 Python 3.9,Flask 2.0,还有那个什么库来着……” 结果装错了版本,又崩了。

2. 可分享性(Shareability)

场景:你写好了 Dockerfile,把它放到 GitHub 上。全世界的开发者都可以 git clone 你的仓库,然后 docker build,就能在你的应用环境里工作。

类比:你发明了一道菜,把菜谱写下来发到网上。别人照着做,就能做出和你一模一样的味道。

3. 版本控制(Version Control)

场景:你的应用升级了,需要装一个新的库。你只需要修改 Dockerfile,加上一行 RUN pip install new-lib,然后重新构建镜像。旧版本的 Dockerfile 还在 Git 历史里,随时可以回退。

类比:你的菜谱有多个版本:v1.0 是番茄炒蛋,v2.0 加了葱花,v3.0 改成了番茄牛腩。每个版本都记录在案,想回退就回退。


一个更真实的例子:带依赖管理的 Dockerfile

上面的例子太简单了。真实项目中,你会有一堆依赖。更好的做法是:

FROM python:3.9-slim

WORKDIR /app

# 先复制依赖文件(这样可以利用 Docker 的缓存机制)
COPY requirements.txt .

# 安装依赖
RUN pip install -r requirements.txt

# 再复制代码(代码经常改,依赖不常改)
COPY . .

CMD ["python", "app.py"]

为什么先复制 requirements.txt 再复制代码? 因为 Docker 在构建镜像时,会缓存每一层。如果依赖文件没变,Docker 会直接使用缓存,跳过安装步骤。这样你改了一行代码,只需要重新复制代码,不需要重新装依赖,构建速度会快很多。

类比:你每次做菜,都要重新切番茄吗?不,番茄可以提前切好放冰箱。只有番茄变了才需要重新切。Docker 的缓存机制就是这个道理。


总结:Dockerfile 就是你的“环境说明书”

指令类比作用
FROM选锅选择基础系统
WORKDIR选操作台设置工作目录
COPY把食材放上台面复制文件到容器
RUN切菜、打蛋执行安装/配置命令
CMD开火炒菜定义容器启动时的命令

一句话记住 Dockerfile:它不是魔法,它只是一份写给 Docker 看的“怎么做”的说明书。你写得越清楚,Docker 做得越准确。

下次当你听到“Dockerfile”这个词时,别想那些复杂的术语。就记住:这是一份菜谱,Docker 是厨师,镜像是一盘菜,容器是正在被享用的那盘菜。

更多推荐