Docker 简介

Dockerfile 构建镜像

学会编写 Dockerfile,把一个 Node.js 应用打包成镜像并运行。

🎯 引言

前面我们一直在用别人做好的镜像(nginx、mysql)。学完这篇文章,你能编写自己的 Dockerfile,把一个 Node.js 应用打包成镜像并运行起来。这意味着你的应用也能像那些官方软件一样,一条命令在任何装有 Docker 的机器上跑起来。


🧱 什么是 Dockerfile

Dockerfile 是一个文本文件,里面写着「如何一步步构建出一个镜像」的指令

可以拿菜谱类比:

  • 菜谱写着:准备什么原料、先做什么、后做什么,照着做就能得到一盘菜。
  • Dockerfile 写着:基于哪个基础镜像、拷贝哪些文件、执行什么命令,照着构建就能得到一个镜像。

有了 Dockerfile,镜像的构建过程就从「口口相传的手工操作」变成了「白纸黑字的可重复流程」,任何人拿到这份文件都能构建出一模一样的镜像。


🛠 准备一个 Node.js 应用

我们先准备一个最简单的 Node.js 应用。本文基于 Node.js 20node:20 镜像编写和验证。新建一个目录 my-app,在里面创建 server.js

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(注意没有后缀名),写入:

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 文件,写上要排除的内容:

.dockerignore
node_modules

构建时 Docker 会自动跳过这些文件。

真实项目里通常会在 Dockerfile 中用 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 3000docker run -p 3000:3000 有什么区别?

EXPOSE 只是声明「这个容器会用到 3000 端口」,起说明作用,不会真的做端口映射。真正让本机能访问容器的,是 docker run 时的 -p 参数,两者缺一不可。

Q3:改了代码后镜像会更新吗?

不会。镜像构建完成后就是固定的,改了代码需要重新执行 docker build 构建新镜像,再重新运行容器。

Q4:CMDRUN 有什么区别?

RUN构建镜像时执行,比如安装依赖,执行结果会固化在镜像里。CMD容器启动时执行,用来启动应用。一句话:RUN 是装软件,CMD 是跑软件。

Q5:构建时报「COPY failed」是什么原因?

常见原因是执行 docker build 的位置不对。命令最后的 . 表示当前目录,请确认你在 Dockerfile 所在目录执行命令。


🧪 小练习

  1. 修改 server.js 的返回内容为你自己的一句话,重新构建镜像并运行,在浏览器里看到新内容:
# 提示:修改代码后需要重新 docker build 和 docker run
# 重新运行前记得先 docker stop 和 docker rm 旧容器
# 请在这里编写命令
  1. 给你的项目加上 .dockerignore,排除 node_modules,然后重新构建,用 docker images 对比镜像大小。

🎉 恭喜你已经会把自己的应用打包成镜像了!下一篇我们学习 Docker Compose,用一个配置文件同时管理多个容器。