首页 / Docker 入门教程 / Dockerfile 入门:构建第一个镜像

Docker 入门教程

Dockerfile 入门:构建第一个镜像

本教程共 25 篇 · 第 11 篇 · 更新于 2026-07-26 · 约 7 分钟阅读

DockerDocker 入门教程Dockerfile镜像构建docker build构建上下文镜像标签

11. Dockerfile 入门:构建第一个镜像

本节目标:搞清楚 Dockerfile 是什么、怎么写,并用 docker build 把它变成一个能跑的镜像。

前面几章我们都在用别人做好的镜像,比如 nginxubuntu。但真到了自己的项目,别人的镜像就不一定够用了。你可能要装额外的依赖、塞进去自己的代码、改默认启动命令。这时候就得自己造镜像。

造镜像有两条路。一条是手动改容器,再用 docker commit 提交成镜像,适合临时救急。另一条就是写一个 Dockerfile,让 Docker 按图纸自动构建。后者才是正经做法,可复现、可版本化、可团队协作。这一节我们就走第二条路。

Dockerfile 到底是什么

Dockerfile 就是一个纯文本文件,里面写着一份「构建说明书」。每一行是一条指令,告诉 Docker:先用哪个基础镜像,再装哪些软件,拷贝哪些文件,最后容器启动时跑什么命令。

打个比方,它像一份装配图纸。基础镜像是半成品零件,RUN 是加工步骤,COPY 是把你的零件装进去,CMD 是最后通电启动。Docker 照着图纸一步步组装,最后产出一个能跑的镜像。

Dockerfile 的指令都是大写开头(小写也认,但约定俗成用大写,方便区分指令和参数)。每个指令对应镜像里的一层,这点我们在第 13 章细讲。

下面是一个最简单的 Dockerfile,能让一个 Python 应用跑起来:

FROM python:3.13
WORKDIR /usr/local/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY src ./src
EXPOSE 8080

RUN useradd app
USER app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]

看不懂没关系,我们一行行拆开讲。先从最常见的几条指令说起。

  • FROM:指定基础镜像,一切构建的起点。
  • WORKDIR:设定容器内的工作目录,后续命令都在这里执行。
  • COPY:把宿主机的文件拷进镜像。
  • RUN:在镜像里执行命令,比如装依赖。
  • EXPOSE:声明容器要监听的端口(只是声明,不等于真的对外开)。
  • USER:指定后续命令以哪个用户身份运行。
  • CMD:容器启动时默认执行的命令。
Note

Dockerfile 这个文件名没有扩展名,就叫 Dockerfile,首字母大写。有些编辑器会自作主张加个 .txt,构建时 Docker 就找不到了,这是新手常踩的坑。

动手写第一个 Dockerfile

光看概念没用,我们来写一个真正能构建的 Dockerfile。以一个 Node.js 应用为例,你不懂 Node 也没关系,重点看构建流程。

第一步:准备项目目录

随便建一个文件夹,里面放点东西。假设目录结构是这样:

my-app/
├── Dockerfile
├── package.json
└── src/
    └── index.js

关键是 Dockerfile 要放在项目根目录,和源码同级。

第二步:写 Dockerfile

用任意文本编辑器创建 Dockerfile,内容如下:

FROM node:22-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "./src/index.js"]

逐行解释:

  1. FROM node:22-alpine:以官方 Node 22 的 Alpine 精简版为基础。Alpine 体积小,适合做基础镜像。
  2. WORKDIR /app:在容器里创建 /app 目录并切过去,后面的操作都在这下面进行。
  3. COPY . .:把宿主机当前目录的所有文件拷到容器的 /app 里。第一个 . 是宿主机路径,第二个 . 是容器内路径。
  4. RUN yarn install --production:在镜像里跑 yarn install,只装生产依赖。
  5. CMD ["node", "./src/index.js"]:容器启动时默认跑 Node 启动脚本。
Warning

这个 Dockerfile 能跑,但还不是最佳实践。比如 COPY . . 放在 RUN 前面会导致每次代码改动都重装依赖,构建很慢。怎么优化是第 13 章的事,先把流程跑通。

第三步:构建镜像

Dockerfile 所在目录执行:

docker build -t my-node-app .

拆开看这个命令:

  • docker build:构建镜像的命令。
  • -t my-node-app:给镜像起个名字(tag)。不写 -t 也能构建,但产出的镜像只有一个 ID,不好记。
  • .:构建上下文(build context)路径,告诉 Docker 去哪找 Dockerfile 和要拷贝的文件。

构建过程会打印一串日志,大致长这样:

$ docker build -t my-node-app .
[+] Building 3.5s (11/11) FINISHED                              docker:desktop-linux
 => [internal] load build definition from Dockerfile           0.0s
 => [internal] load metadata for docker.io/library/node:22-alpine  0.0s
 => [1/4] FROM docker.io/library/node:22-alpine                0.0s
 => [2/4] WORKDIR /app                                         0.0s
 => [3/4] COPY . .                                             0.1s
 => [4/4] RUN yarn install --production                        3.2s
 => exporting to image                                         0.1s
 => => writing image sha256:9924dfd9350407b3df01d1a0e1033b1e5  0.0s

每一步对应 Dockerfile 里的一条指令。看到 FINISHED 就说明构建成功了。最后那串 sha256:... 是镜像的唯一 ID。

构建上下文那点事

docker build 最后那个 . 很重要,它不是 Dockerfile 的路径,而是「构建上下文」的路径。

构建上下文就是 Docker 能看到的那些文件。Dockerfile 里的 COPYADD 指令只能从上下文里拷文件,拷不到上下文之外的东西。Docker 会先把整个上下文目录打包发给守护进程(daemon),再开始构建。

所以上下文别选太大。如果你在项目根目录构建,而根目录里有个几 GB 的 node_modules 或日志目录,每次构建都要打包上传,慢得要命。

Tip

一个常见做法是在项目根目录放一个 .dockerignore 文件,写上不想传给 Docker 的目录,比如 node_modules.git*.log。作用和 .gitignore 类似。第 13 章会展开讲。

给镜像打标签

光有名字还不够,镜像还有「标签」(tag)的概念。完整镜像名的结构是这样的:

[HOST[:PORT]/]PATH[:TAG]
  • HOST:仓库地址,不写默认是 docker.io(Docker Hub)。
  • PATH:路径,Docker Hub 上是 命名空间/仓库名,没命名空间时默认 library(官方镜像)。
  • TAG:标签,通常是版本号,不写默认 latest

举几个例子:

  • nginx 等价于 docker.io/library/nginx:latest
  • docker/welcome-to-docker 等价于 docker.io/docker/welcome-to-docker:latest
  • ghcr.io/dockersamples/vote:pr-311 是 GitHub Container Registry 上的镜像

构建时可以一步到位打全名:

docker build -t myname/my-node-app:1.0 .

如果构建时忘了打标签,事后也能用 docker image tag 补:

docker image tag my-node-app myname/my-node-app:1.0

这个命令不会复制镜像,只是给同一个镜像多挂一个名字。两个标签指向同一份镜像数据。

验证你的镜像

构建完了,得确认它真的能用。

先看看镜像在不在列表里:

docker image ls

输出大概是这样:

REPOSITORY     TAG       IMAGE ID       CREATED          SIZE
my-node-app    latest    746c7e06537f   24 seconds ago   354MB

想看镜像是怎么一层层构建出来的,用 docker image history

docker image history my-node-app

它会列出镜像每一层的来源、创建命令和大小。你新增的层在最上面,基础镜像继承下来的层在下面,能看到清晰的构建历史。

最后跑起来试试:

docker run -p 3000:3000 my-node-app

能正常启动、访问到服务,第一个镜像就算构建成功了。

Dockerfile vs docker commit

最后说一句,为什么推荐 Dockerfile 而不是 docker commit

docker commit 是把一个改过的容器快照成镜像。临时用没问题,但它有个致命缺点:不可复现。你在这个容器里手动装了啥、改了啥,只有你自己知道,别人拿到镜像根本看不出来。过两个月你自己也忘了。

Dockerfile 不一样,它就是一份可读、可版本控制的说明书。任何人拿到这个文件和源码,都能构建出一模一样的镜像。团队协作、CI/CD 流水线,都靠它。

Note

docker commit 也不是完全没用。比如你在排查容器问题时手动装了几个调试工具,想保存现场给别人看,这种临时场景用 commit 很方便。但正式发布的镜像,一定要走 Dockerfile。

下一章我们把 Dockerfile 里常用的指令逐个讲透,重点辨析几对容易混淆的指令,比如 CMDENTRYPOINTCOPYADD