3、第一个Node.js应用:创建Express应用,编写Dockerfile,构建镜像并运行容器
好,咱们直接动手。很多新手学容器化,容易陷入「先学一堆理论再动手」的误区。我个人习惯是——先跑起来一个东西,哪怕它再简单,也能帮你建立直观感受。今天我们就拿最经典的Express框架开刀。
3.1 初始化项目与安装依赖
先找个干净目录,我一般叫它 my-express-app。打开终端,敲下面几行:
mkdir my-express-app
cd my-express-app
npm init -y
npm install express
嗯,这里要注意:npm init -y 会生成一个默认的 package.json。如果你在团队里,我建议把 name 和 version 改得规范点,不然以后镜像打出来名字怪怪的。
3.2 编写Express应用
在项目根目录创建 app.js,内容如下:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello from Dockerized Node.js!');
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
这段代码没什么特别的,就一个根路由返回字符串。但我想强调一点:用 process.env.PORT 而不是硬编码端口。为什么?因为容器运行时,宿主机端口和容器端口往往不一样,你想想看,如果写死了3000,后面映射端口时就会很别扭。我在项目中遇到过好几次,开发环境好好的,一上容器就报端口冲突,最后发现是代码里写死了端口号。
3.3 编写Dockerfile
接下来是重头戏。在项目根目录创建 Dockerfile(注意没有后缀名):
# 使用官方Node.js LTS版本作为基础镜像
FROM node:18-alpine
# 设置工作目录
WORKDIR /usr/src/app
# 先复制package.json和package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm install --production
# 复制源代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["node", "app.js"]
这里有个小细节:为什么先复制 package*.json,再复制源代码? 说白了,这是利用Docker的层缓存机制。你想想,依赖安装通常很慢,而源代码改动很频繁。如果先复制源代码,每次改一行代码都要重新 npm install,那构建速度就太慢了。我刚开始写Dockerfile时没注意这个顺序,每次改代码都要等两分钟装依赖,后来被同事提醒才改过来。
npm ci 代替 npm install,构建会更稳定。因为 npm ci 会严格按照 package-lock.json 安装,避免版本漂移。
3.4 构建镜像
在项目根目录执行:
docker build -t my-express-app:1.0 .
解释一下参数:
-t给镜像打标签,格式是名称:版本.指定构建上下文为当前目录
构建过程中你会看到每一层(Layer)的执行日志。如果一切顺利,最后会显示 Successfully tagged my-express-app:1.0。你可以用 docker images 查看刚构建的镜像。
node_modules 目录。虽然Docker默认会忽略 .dockerignore 中指定的文件,但如果你没配,建议创建一个 .dockerignore 文件,写入 node_modules 和 .git。否则构建时会把本地几十兆的依赖包也打包进去,镜像体积会大得离谱。
3.5 运行容器
镜像构建好了,接下来运行容器:
docker run -d -p 8080:3000 --name my-app my-express-app:1.0
参数说明:
-d后台运行(detached模式)-p 8080:3000将宿主机的8080端口映射到容器的3000端口--name给容器起个名字,方便管理
运行后,打开浏览器访问 http://localhost:8080,你应该能看到 Hello from Dockerized Node.js! 的响应。
如果想查看容器日志:
docker logs my-app
停止并删除容器:
docker stop my-app
docker rm my-app
3.6 验证与调试
有时候容器启动后没反应,别慌。我常用的排查三板斧:
- 看日志:
docker logs 容器名,看有没有报错信息 - 进容器:
docker exec -it 容器名 sh,进去看看文件结构对不对,依赖装没装 - 检查端口:
docker port 容器名,确认端口映射是否生效
我曾经有一次折腾了半天,发现容器启动后马上退出,最后用 docker logs 一看,原来是 app.js 里有个语法错误。嗯,这种低级错误最容易让人抓狂。
3.7 本章小结
今天我们走完了从零到一的完整流程:
| 步骤 | 关键命令/文件 | 注意事项 |
|---|---|---|
| 初始化项目 | npm init -y |
规范package.json的name字段 |
| 编写应用 | app.js |
使用环境变量读取端口 |
| 编写Dockerfile | Dockerfile |
注意文件复制顺序,利用缓存 |
| 构建镜像 | docker build -t xxx . |
配置.dockerignore |
| 运行容器 | docker run -d -p xxx:xxx |
端口映射不要搞反 |
说白了,容器化就是把你的应用「打包」成一个标准化的单元。今天这个例子虽然简单,但背后的思想——环境一致性、依赖隔离、快速部署——是后续所有复杂方案的基础。下一章我们会在这个基础上,加入环境变量管理和多阶段构建,让镜像更小、更安全。
- Dockerfile的指令顺序影响构建速度
- 端口映射时,宿主机端口在前,容器端口在后
- 永远不要忘记
.dockerignore