Docker 是一个开源的应用容器引擎,它允许开发者将应用及其依赖打包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上。容器化使得应用的环境一致性、部署和扩展变得前所未有的简单。本章将带领你一步步将 Node.js 应用 Docker 化,并介绍生产环境的最佳实践。
Dockerfile 是一个文本文件,包含了构建镜像所需的指令。以下是一个基本的 Node.js 应用 Dockerfile:
# 使用官方 Node.js 镜像作为基础
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json(如果有)
COPY package*.json ./
# 安装生产依赖
RUN npm ci --only=production
# 复制应用源代码
COPY . .
# 暴露应用端口(假设应用监听 3000 端口)
EXPOSE 3000
# 启动命令
CMD ["node", "server.js"]
说明:
FROM 指定基础镜像,这里使用基于 Alpine Linux 的 Node.js 镜像,体积更小。WORKDIR 设置容器内的工作目录,后续命令都在此目录执行。COPY 复制文件。先复制 package*.json 可以充分利用 Docker 缓存:如果依赖文件没变,则跳过 npm ci。RUN npm ci --only=production 安装生产依赖。--only=production 只安装 dependencies,不安装 devDependencies。COPY 复制其余源代码。EXPOSE 声明容器内应用监听的端口,仅起文档作用。CMD 指定容器启动时执行的命令。与 .gitignore 类似,.dockerignore 用于排除不需要复制到镜像中的文件,可以减小镜像体积并提高构建速度。示例:
.git
node_modules
npm-debug.log
.env
Dockerfile
.dockerignore
.gitignore
README.md
.vscode
coverage
tests
在 Dockerfile 所在目录执行以下命令构建镜像:
docker build -t my-node-app .
运行容器:
docker run -p 3000:3000 -d --name my-app my-node-app
-p 3000:3000 将宿主机的 3000 端口映射到容器的 3000 端口。-d 后台运行。--name 指定容器名称。
多阶段构建可以进一步减小镜像体积。第一阶段用于构建和安装依赖(包括开发依赖),第二阶段只复制必要的文件。以下是一个使用多阶段构建的示例:
# 第一阶段:构建
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci # 安装所有依赖(包括开发依赖)
COPY . .
RUN npm run build # 如果有构建步骤(如 TypeScript 编译)
# 第二阶段:生产
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist # 假设构建输出在 dist 目录
EXPOSE 3000
CMD ["node", "dist/server.js"]
最终镜像只包含运行所需的文件,避免了开发依赖和源代码泄露。
对于多容器应用(如 Node.js + Redis + MongoDB),docker-compose 可以简化管理。创建 docker-compose.yml 文件:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_URL=mongodb://mongo:27017/mydb
- REDIS_URL=redis://redis:6379
depends_on:
- mongo
- redis
volumes:
- ./uploads:/app/uploads # 持久化上传文件
mongo:
image: mongo:6
volumes:
- mongo-data:/data/db
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
volumes:
mongo-data:
redis-data:
然后使用 docker-compose up -d 启动所有服务。Compose 会自动创建网络,使得服务可以通过服务名(如 mongo)相互访问。
在生产环境中,敏感信息不应硬编码在 Dockerfile 或代码中。有几种传递环境变量的方式:
docker run -e "KEY=VALUE" 直接传递。--env-file 指定环境变量文件。environment 或 env_file 指定。示例:使用 .env 文件(确保该文件在 .dockerignore 中,避免包含在镜像内)
docker run -p 3000:3000 --env-file .env my-node-app
容器是无状态的,当容器删除时,内部数据也会丢失。使用卷(volume)可以持久化数据。在 docker run 中可以通过 -v 参数指定:
docker run -v /host/path:/container/path ...
在 docker-compose 中可以使用顶级 volumes 定义命名卷(如上面的 mongo-data),然后挂载到服务。
node:18-alpine,避免不必要的系统库。package.json 安装依赖,再复制源码。RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
USER nodejs
HEALTHCHECK 指令,让容器引擎可以监控应用状态。假设我们有一个简单的 Express 应用,结构如下:
my-express-app/
├── package.json
├── server.js
└── .dockerignore
server.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello from Dockerized Express!');
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
USER nodejs
CMD ["node", "server.js"]
.dockerignore
node_modules
npm-debug.log
.git
.env
Dockerfile
.dockerignore
构建并运行:
docker build -t express-app .
docker run -p 3000:3000 -d --name my-express express-app
访问 http://localhost:3000 即可看到结果。
构建好的镜像可以推送到容器镜像仓库(如 Docker Hub、阿里云容器镜像服务),然后通过云平台的容器服务(如 AWS ECS、阿里云 ECI、Google Cloud Run)部署。这些平台会自动拉取镜像并运行容器,并提供负载均衡、自动扩缩容等功能。
示例:推送到 Docker Hub
docker tag express-app yourusername/express-app:latest
docker push yourusername/express-app:latest
docker logs <container> 查看日志。EXPOSE 与实际监听端口一致,且宿主机端口未被占用。docker run -v $(pwd):/app -p 3000:3000 ...。docker history 分析每层大小。