Dockerfile 入门:构建第一个镜像
本教程共 25 篇 · 第 11 篇 · 更新于 2026-07-26 · 约 7 分钟阅读
11. Dockerfile 入门:构建第一个镜像
本节目标:搞清楚 Dockerfile 是什么、怎么写,并用
docker build把它变成一个能跑的镜像。
前面几章我们都在用别人做好的镜像,比如 nginx、ubuntu。但真到了自己的项目,别人的镜像就不一定够用了。你可能要装额外的依赖、塞进去自己的代码、改默认启动命令。这时候就得自己造镜像。
造镜像有两条路。一条是手动改容器,再用 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:容器启动时默认执行的命令。
NoteDockerfile 这个文件名没有扩展名,就叫
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"]
逐行解释:
FROM node:22-alpine:以官方 Node 22 的 Alpine 精简版为基础。Alpine 体积小,适合做基础镜像。WORKDIR /app:在容器里创建/app目录并切过去,后面的操作都在这下面进行。COPY . .:把宿主机当前目录的所有文件拷到容器的/app里。第一个.是宿主机路径,第二个.是容器内路径。RUN yarn install --production:在镜像里跑yarn install,只装生产依赖。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 里的 COPY、ADD 指令只能从上下文里拷文件,拷不到上下文之外的东西。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:latestdocker/welcome-to-docker等价于docker.io/docker/welcome-to-docker:latestghcr.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 里常用的指令逐个讲透,重点辨析几对容易混淆的指令,比如 CMD 和 ENTRYPOINT、COPY 和 ADD。