Dockerfile 构建镜像
🎯 引言
前面我们一直在用别人做好的镜像(nginx、mysql)。学完这篇文章,你能编写自己的 Dockerfile,把一个 Node.js 应用打包成镜像并运行起来。这意味着你的应用也能像那些官方软件一样,一条命令在任何装有 Docker 的机器上跑起来。
🧱 什么是 Dockerfile
Dockerfile 是一个文本文件,里面写着「如何一步步构建出一个镜像」的指令。
可以拿菜谱类比:
- 菜谱写着:准备什么原料、先做什么、后做什么,照着做就能得到一盘菜。
- Dockerfile 写着:基于哪个基础镜像、拷贝哪些文件、执行什么命令,照着构建就能得到一个镜像。
有了 Dockerfile,镜像的构建过程就从「口口相传的手工操作」变成了「白纸黑字的可重复流程」,任何人拿到这份文件都能构建出一模一样的镜像。
🛠 准备一个 Node.js 应用
我们先准备一个最简单的 Node.js 应用。本文基于 Node.js 20、node:20 镜像编写和验证。新建一个目录 my-app,在里面创建 server.js:
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello from Docker');
});
server.listen(3000, () => {
console.log('server is running on port 3000');
});
这个应用不依赖任何第三方包,用 Node.js 内置的 http 模块起一个服务,监听 3000 端口。
🧱 Dockerfile 常用指令
在 server.js 同级目录创建文件 Dockerfile(注意没有后缀名),写入:
FROM node:20
WORKDIR /app
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
逐行来看每条指令的作用:
FROM node:20:基于node:20镜像构建。我们的应用需要 Node.js 环境,直接站在官方镜像的肩膀上。WORKDIR /app:把容器内的/app目录设为工作目录,后续操作都在这里进行。COPY . .:把当前目录(本机的项目文件)拷贝到容器的工作目录里。EXPOSE 3000:声明容器会监听 3000 端口,起到说明文档的作用。CMD ["node", "server.js"]:容器启动时执行的命令,也就是启动我们的应用。
🛠 构建并运行
在 Dockerfile 所在目录执行构建命令:
docker build -t my-web .
-t my-web:给构建出的镜像起名叫my-web。.:表示以当前目录作为构建上下文,Docker 会从这里读取 Dockerfile 和要拷贝的文件。
构建完成后,用 docker images 能看到 my-web 镜像。接下来像运行任何镜像一样运行它:
docker run -d --name app -p 3000:3000 my-web
打开浏览器访问 http://localhost:3000,看到「Hello from Docker」,说明你自己的镜像已经正常工作了。这个镜像发给任何装了 Docker 的人,他运行同样的命令就能得到一模一样的应用。
🪤 镜像体积与 .dockerignore
COPY . . 会把当前目录的所有文件都拷进镜像。如果项目里有 node_modules,会整个被拷进去,镜像又大又慢。
解决方法和 .gitignore 一样,在项目根目录建一个 .dockerignore 文件,写上要排除的内容:
node_modules
构建时 Docker 会自动跳过这些文件。
RUN npm install 在容器内安装依赖,而不是拷贝本机的 node_modules。本课程示例没有第三方依赖,所以不需要 RUN 指令,了解有这个用法即可。🧾 小节总结
- Dockerfile 是描述镜像构建步骤的文本文件,像菜谱一样让构建过程可重复。
- 常用指令:
FROM指定基础镜像、WORKDIR设工作目录、COPY拷贝文件、EXPOSE声明端口、CMD指定启动命令。 - 用
docker build -t my-web .构建镜像,用docker run -d -p 3000:3000 my-web运行。 - 用
.dockerignore排除node_modules等不需要拷贝的文件,控制镜像体积。
❓ 知识问答
Q1:为什么需要 FROM,不能从零开始吗?
镜像是一层层叠加的,几乎都要基于一个基础镜像。FROM node:20 意味着你的镜像里自带了装好的 Node.js 环境,省去了自己安装的麻烦。
Q2:EXPOSE 3000 和 docker run -p 3000:3000 有什么区别?
EXPOSE 只是声明「这个容器会用到 3000 端口」,起说明作用,不会真的做端口映射。真正让本机能访问容器的,是 docker run 时的 -p 参数,两者缺一不可。
Q3:改了代码后镜像会更新吗?
不会。镜像构建完成后就是固定的,改了代码需要重新执行 docker build 构建新镜像,再重新运行容器。
Q4:CMD 和 RUN 有什么区别?
RUN 在构建镜像时执行,比如安装依赖,执行结果会固化在镜像里。CMD 在容器启动时执行,用来启动应用。一句话:RUN 是装软件,CMD 是跑软件。
Q5:构建时报「COPY failed」是什么原因?
常见原因是执行 docker build 的位置不对。命令最后的 . 表示当前目录,请确认你在 Dockerfile 所在目录执行命令。
🧪 小练习
- 修改
server.js的返回内容为你自己的一句话,重新构建镜像并运行,在浏览器里看到新内容:
# 提示:修改代码后需要重新 docker build 和 docker run
# 重新运行前记得先 docker stop 和 docker rm 旧容器
# 请在这里编写命令
- 给你的项目加上
.dockerignore,排除node_modules,然后重新构建,用docker images对比镜像大小。
🎉 恭喜你已经会把自己的应用打包成镜像了!下一篇我们学习 Docker Compose,用一个配置文件同时管理多个容器。
