Docker
Docker 是一个开源的容器化平台,用于自动化应用程序的部署、扩展和管理。
Overview
Docker 通过容器技术,将应用及其依赖打包成标准化的单元,实现"一次构建,到处运行"。
核心概念
- 镜像(Image) - 只读的应用模板
- 容器(Container) - 镜像的运行实例
- Dockerfile - 构建镜像的脚本
- Docker Compose - 多容器应用编排工具
容器生命周期与前台运行
1. 核心原理:容器 ≠ 虚拟机
Docker 容器的本质是宿主机上的一个进程,不是虚拟机。Docker 通过监控容器的 主进程(PID 1) 来管理容器的生命周期:
┌─────────────────────────────────────┐
│ 宿主机 │
│ │
│ ┌───────────────────────────────┐ │
│ │ Docker 容器 │ │
│ │ │ │
│ │ PID 1: nginx (主进程) ← 监控 │ │
│ │ ├─ worker process 1 │ │
│ │ ├─ worker process 2 │ │
│ │ └─ worker process 3 │ │
│ │ │ │
│ │ 规则:主进程退出 = 容器停止 │ │
│ └───────────────────────────────┘ │
│ │
└─────────────────────────────────────┘生命周期规则:
- 主进程存活 → 容器运行 ✅
- 主进程退出 → 容器停止 ❌
2. 为什么需要前台运行?
以 Nginx 为例,对比两种运行模式:
后台模式(Daemon,默认)
传统 Linux 服务器上,Nginx 以守护进程方式运行:
# 在普通服务器上
$ nginx # 启动 Nginx
$ echo $? # 主进程立即退出,返回 0
0
$ ps aux | grep nginx # Nginx 在后台运行
root 123 nginx: master process
nginx 124 nginx: worker process工作流程:
- 执行
nginx命令 - 主进程 fork 出 master 和 worker 进程
- 主进程立即退出(返回到 shell)
- Master 和 worker 进程在后台持续运行
这种模式在普通服务器上没问题,但在 Docker 容器中:
FROM nginx:alpine
CMD ["nginx"] # ❌ 容器会立即退出$ docker run -d nginx:alpine nginx
$ docker ps # 容器已停止,查不到
$ docker ps -a # 查看已停止的容器
CONTAINER ID STATUS
abc123 Exited (0) 1 second ago为什么容器退出?
- Docker 只监控主进程(PID 1)
nginx命令启动后主进程立即退出- Docker 检测到主进程退出 → 认为任务完成 → 停止容器
前台模式(Foreground,daemon off)
让 Nginx 以前台进程运行:
FROM nginx:alpine
# ✅ 使用 daemon off 让主进程持续运行
CMD ["nginx", "-g", "daemon off;"]$ docker run -d nginx:alpine nginx -g "daemon off;"
$ docker ps # 容器正常运行
CONTAINER ID STATUS
abc123 Up 5 seconds工作流程:
- 执行
nginx -g "daemon off;"命令 - Nginx 以前台模式启动
- 主进程持续运行(不退出)
- Docker 检测到主进程存活 → 容器保持运行
3. 其他服务的前台运行
所有容器化的服务都需要以前台模式运行:
PostgreSQL(Jungle 项目实例)
# docker-compose.dev.yml
services:
postgres_dev:
container_name: jungle_postgres_dev
image: postgres:17-alpine
ports:
- '5432:5432'
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=mysecretpassword
- POSTGRES_DB=junglePostgreSQL 官方镜像的默认启动命令:
# postgres:17-alpine 镜像内部
CMD ["postgres"] # ✅ postgres 进程本身就是前台运行说明:
- PostgreSQL 的
postgres命令默认以前台模式运行 - 与 MySQL 的
mysqld命令类似,都是前台进程 - 不需要额外配置,直接使用官方镜像即可
Redis(Jungle 项目实例)
# docker-compose.dev.yml
services:
redis_dev:
container_name: jungle_redis_dev
image: redis:7-alpine
ports:
- '6379:6379'
volumes:
- redis_data:/dataRedis 官方镜像的默认启动命令:
# redis:7-alpine 镜像内部
CMD ["redis-server"] # ✅ 默认前台运行(--daemonize no)说明:
- Redis 官方镜像已经配置为前台运行模式
- 默认禁用了
daemonize选项(daemonize no) - 直接使用官方镜像即可
Node.js 应用(Jungle 项目实例)
# Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
RUN npm install -g pnpm
COPY package.json pnpm-lock.yaml ./
RUN pnpm install
COPY . .
# NestJS 开发模式
CMD ["pnpm", "start:dev"] # ✅ Node 进程持续运行说明:
- Node.js 应用(Express、NestJS、Koa 等)默认都是前台运行
node server.js或pnpm start:dev启动的进程会持续运行- 应用监听端口,等待请求,主进程不会退出
自定义脚本
如果需要运行 shell 脚本作为主进程:
FROM alpine
COPY start.sh /start.sh
# 使用 exec 确保脚本中的进程成为 PID 1
CMD ["sh", "-c", "exec /start.sh"]4. 验证主进程
进入容器查看 PID 1:
# 进入容器
docker exec -it bush_app sh
# 查看进程树
ps aux
# PID USER COMMAND
# 1 root nginx: master process nginx -g daemon off;
# 7 nginx nginx: worker process
# 8 nginx nginx: worker process
# 验证 PID 1 是 nginx 主进程
cat /proc/1/cmdline
# nginx-gdaemon off;5. 实战建议
使用官方镜像 - 官方镜像已经配置好前台运行模式
dockerfileFROM nginx:alpine # 已配置 daemon off FROM redis:alpine # 已配置 daemonize no FROM postgres:17-alpine # 已配置 foreground mode自定义服务 - 确保主进程不退出
dockerfile# ❌ 错误:脚本执行完就退出 CMD ["sh", "-c", "echo 'Starting...' && exit 0"] # ✅ 正确:主进程持续运行 CMD ["sh", "-c", "echo 'Starting...' && tail -f /dev/null"]调试容器退出问题
bash# 查看容器退出状态 docker ps -a # 查看容器日志 docker logs container_name # 查看容器退出码 docker inspect container_name | grep ExitCode
实战案例:Bush & Jungle 容器化实践
本节以真实项目 Bush(React 前端)和 Jungle(NestJS 后端)为例,展示完整的容器化开发和部署方案。
项目架构
开发环境(完全容器化)
┌──────────────────────────────────────────────────────────────────────┐
│ Docker 网络:jungle_default(默认桥接网络) │
│ │
│ ┌────────────────────────┐ │
│ │ Bush 容器 │ │
│ │ bush_app_dev │ │
│ │ ───────────────────── │ │
│ │ 镜像: node:20-alpine │ │
│ │ 端口: 8000 → 8000 │ ← 浏览器访问 localhost:8000 │
│ │ 服务: UmiJS Dev Server │ │
│ │ 特性: HMR 热更新 │ │
│ │ │ │
│ │ Proxy 配置: │ │
│ │ /api/* → jungle_app_dev:3000 │
│ └────────────┬───────────┘ │
│ │ HTTP 请求 │
│ │ │
│ ┌────────────▼───────────┐ │
│ │ Jungle 容器 │ │
│ │ jungle_app_dev │ │
│ │ ───────────────────── │ │
│ │ 镜像: node:20-alpine │ │
│ │ 端口: 3000 → 3000 │ │
│ │ 服务: NestJS API │ │
│ │ 特性: 热重载 │ │
│ │ │ │
│ │ 依赖健康检查: │ │
│ │ - PostgreSQL Ready │ │
│ │ - Redis Ready │ │
│ └───┬──────────────┬─────┘ │
│ │ │ │
│ │ TypeORM │ Redis Client │
│ │ │ │
│ ┌───▼────────┐ ┌──▼─────────┐ │
│ │ PostgreSQL │ │ Redis │ │
│ │ 容器 │ │ 容器 │ │
│ │ ────────── │ │ ────────── │ │
│ │ postgres_dev│ │ redis_dev │ │
│ │ │ │ │ │
│ │ 镜像: │ │ 镜像: │ │
│ │ postgres: │ │ redis: │ │
│ │ 17-alpine │ │ 7-alpine │ │
│ │ │ │ │ │
│ │ 端口: │ │ 端口: │ │
│ │ 5432→5432 │ │ 6379→6379 │ │
│ │ │ │ │ │
│ │ 数据持久化: │ │ 数据持久化: │ │
│ │ postgres_ │ │ redis_data │ │
│ │ data volume│ │ volume │ │
│ │ │ │ │ │
│ │ 健康检查: │ │ 健康检查: │ │
│ │ pg_isready │ │ redis-cli │ │
│ │ │ │ ping │ │
│ └────────────┘ └────────────┘ │
│ │
│ 卷(Volumes): │
│ - bush_node_modules: Bush 依赖缓存 │
│ - app_node_modules: Jungle 依赖缓存 │
│ - postgres_data: 数据库数据持久化 │
│ - redis_data: 缓存数据持久化 │
└──────────────────────────────────────────────────────────────────────┘通信流程说明:
- 浏览器 → Bush:用户访问
http://localhost:8000 - Bush → Jungle:前端通过 UmiJS proxy 将
/api/*请求转发到http://jungle_app_dev:3000(容器名解析) - Jungle → PostgreSQL:后端通过 TypeORM 连接数据库(
postgres_dev:5432) - Jungle → Redis:后端通过 ioredis 连接缓存(
redis_dev:6379) - 依赖启动顺序:PostgreSQL/Redis 健康检查通过 → Jungle 启动 → Bush 启动
生产环境
┌──────────────────────────────────────────────────────────────────────┐
│ Docker 网络:jungle_default │
│ │
│ ┌────────────────────────┐ │
│ │ Bush 容器 │ │
│ │ bush_app │ │
│ │ ───────────────────── │ │
│ │ 镜像: nginx:alpine │ │
│ │ 端口: 80 → 80 │ ← 客户端访问 │
│ │ 服务: Nginx │ │
│ │ │ │
│ │ 功能: │ │
│ │ 1. 静态文件服务 │ │
│ │ / → /usr/share/ │ │
│ │ nginx/html/ │ │
│ │ │ │
│ │ 2. API 反向代理 │ │
│ │ /api/* → jungle_app:3000 │
│ │ (重写: /api/users → /users) │
│ │ │ │
│ │ 3. 用户文件服务 │ │
│ │ /static/* → 共享目录 │ │
│ └────────────┬───────────┘ │
│ │ HTTP 请求(反向代理) │
│ │ │
│ ┌────────────▼───────────┐ │
│ │ Jungle 容器 │ │
│ │ jungle_app │ │
│ │ ───────────────────── │ │
│ │ 镜像: node:20-alpine │ │
│ │ 端口: 3000 (内部) │ │
│ │ 服务: NestJS API │ │
│ │ 模式: 生产模式 │ │
│ └───┬──────────────┬─────┘ │
│ │ │ │
│ │ │ │
│ ┌───▼────────┐ ┌──▼─────────┐ │
│ │ PostgreSQL │ │ Redis │ │
│ │ 容器 │ │ 容器 │ │
│ │ ────────── │ │ ────────── │ │
│ │ jungle_ │ │ jungle_ │ │
│ │ postgres │ │ redis │ │
│ │ │ │ │ │
│ │ 端口: │ │ 端口: │ │
│ │ 5432(内部) │ │ 6379(内部) │ │
│ │ │ │ │ │
│ │ 数据持久化: │ │ 数据持久化: │ │
│ │ 宿主机目录 │ │ 宿主机目录 │ │
│ └────────────┘ └────────────┘ │
└──────────────────────────────────────────────────────────────────────┘配置文件详解
1. Jungle 后端配置
docker-compose.dev.yml
services:
# 后端应用容器
app_dev:
container_name: jungle_app_dev
build:
context: .
dockerfile: Dockerfile.dev
ports:
- '3000:3000' # 映射到宿主机,便于调试
volumes:
- .:/app # 代码实时同步
- app_node_modules:/app/node_modules # 依赖隔离
env_file:
- .env.development # 环境变量配置
depends_on:
postgres_dev:
condition: service_healthy # 等待数据库就绪
redis_dev:
condition: service_healthy # 等待缓存就绪
# PostgreSQL 数据库容器
postgres_dev:
container_name: jungle_postgres_dev
image: postgres:17-alpine
ports:
- '5432:5432'
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=mysecretpassword
- POSTGRES_DB=jungle
volumes:
- postgres_data:/var/lib/postgresql/data # 数据持久化
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s # 每 5 秒检查一次
timeout: 5s # 超时时间
retries: 10 # 重试 10 次
start_period: 10s # 启动宽限期
# Redis 缓存容器
redis_dev:
container_name: jungle_redis_dev
image: redis:7-alpine
ports:
- '6379:6379'
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 30
volumes:
postgres_data: # 命名卷:数据库数据
redis_data: # 命名卷:缓存数据
app_node_modules: # 命名卷:依赖缓存Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
# 安装 pnpm
RUN npm config set registry https://registry.npmmirror.com \
&& npm install -g pnpm \
&& pnpm config set registry https://registry.npmmirror.com
# 复制依赖文件
COPY package.json pnpm-lock.yaml ./
# 安装依赖
RUN pnpm install
# 暴露开发服务器端口
EXPOSE 3000
# 启动 NestJS 开发服务器(热重载)
CMD ["pnpm", "start:dev"]package.json
{
"scripts": {
"c:up": "docker compose -f docker-compose.dev.yml up --build",
"c:down": "docker compose -f docker-compose.dev.yml down -v",
"start:dev": "NODE_ENV=development nest start --watch"
}
}2. Bush 前端配置
docker-compose.dev.yml
version: '3.8'
services:
bush_dev:
container_name: bush_app_dev
build:
context: .
dockerfile: Dockerfile.dev
ports:
- '8000:8000'
volumes:
- .:/app # 代码实时同步
- bush_node_modules:/app/node_modules # 依赖隔离
environment:
- REACT_APP_ENV=dev
- NODE_ENV=development
networks:
- jungle_default # 加入后端网络,可以访问 jungle_app_dev
volumes:
bush_node_modules:
networks:
jungle_default:
external: true # 使用外部网络(由 Jungle 创建)Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
# 安装 pnpm
RUN npm config set registry https://registry.npmmirror.com \
&& npm install -g pnpm \
&& pnpm config set registry https://registry.npmmirror.com
# 复制依赖文件
COPY package.json pnpm-lock.yaml ./
# 安装依赖
RUN pnpm install
# 暴露开发服务器端口
EXPOSE 8000
# 启动 UmiJS 开发服务器(HMR)
CMD ["pnpm", "start:dev"]config/proxy.ts(UmiJS 开发代理)
export default {
dev: {
'/api/': {
// 通过容器名访问后端(Docker DNS 解析)
target: 'http://jungle_app_dev:3000',
changeOrigin: true,
pathRewrite: { '^/api': '' }, // /api/users → /users
},
},
};package.json
{
"scripts": {
"dev": "pnpm c:up",
"c:up": "docker compose -f docker-compose.dev.yml up --build",
"c:down": "docker compose -f docker-compose.dev.yml down -v",
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev"
}
}3. 生产环境配置
Bush Dockerfile(多阶段构建)
# ========================================
# 第一阶段:构建阶段
# ========================================
FROM node:20-alpine AS builder
WORKDIR /app
# 安装 pnpm
RUN npm config set registry https://registry.npmmirror.com \
&& npm install -g pnpm \
&& pnpm config set registry https://registry.npmmirror.com
# 复制依赖文件
COPY package.json pnpm-lock.yaml ./
# 安装依赖
RUN pnpm install --frozen-lockfile
# 复制源代码
COPY . .
# 构建生产版本(在容器内构建,确保环境一致)
RUN pnpm run build
# ========================================
# 第二阶段:生产阶段
# ========================================
FROM nginx:alpine
# 从构建阶段复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html/
# 复制 Nginx 配置文件
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露 80 端口
EXPOSE 80
# 启动 Nginx(前台运行)
CMD ["nginx", "-g", "daemon off;"]nginx.conf
server {
listen 80;
server_name 1px.club;
# 前端静态文件
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html; # SPA 路由支持
}
# API 反向代理
location /api/ {
# Docker 内部 DNS 解析器
resolver 127.0.0.11 valid=30s;
set $jungle_service http://jungle_app:3000;
proxy_pass $jungle_service;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 路径重写:/api/users → /users
rewrite ^/api/(.*)$ /$1 break;
}
# 用户上传文件
location /static/ {
alias /app/shared_uploads/;
}
}开发流程
启动开发环境
# 1. 启动 Jungle 后端(会自动启动 PostgreSQL 和 Redis)
cd jungle
pnpm c:up
# 查看启动日志,确认健康检查通过
docker compose -f docker-compose.dev.yml logs -f
# 2. 启动 Bush 前端(会自动加入 jungle_default 网络)
cd bush
pnpm dev
# 3. 验证
# - 浏览器访问 http://localhost:8000
# - 前端可以通过 /api/* 访问后端
# - 后端可以访问 PostgreSQL 和 Redis常用命令
# 查看所有容器状态
docker ps
# 查看网络连接
docker network inspect jungle_default
# 进入容器调试
docker exec -it jungle_app_dev sh
docker exec -it bush_app_dev sh
# 查看容器日志
docker logs -f jungle_app_dev
docker logs -f jungle_postgres_dev
# 停止并清理
cd jungle && pnpm c:down
cd bush && pnpm c:down生产部署
# 1. 构建镜像(多阶段构建)
cd bush
docker compose build
# 2. 启动服务
docker compose up -d
# 3. 查看状态
docker compose ps
# 4. 查看日志
docker compose logs -f关键技术点
1. 容器网络通信
开发环境:
- Bush 通过
external: true加入 Jungle 创建的jungle_default网络 - 容器间通过容器名互相访问(Docker 内部 DNS)
- 端口映射到宿主机,便于调试
生产环境:
- 所有容器在同一 Docker 网络
- Bush 不映射端口到宿主机(Nginx 监听 80)
- 数据库和缓存仅内部访问
2. 健康检查与依赖管理
depends_on:
postgres_dev:
condition: service_healthy # 必须等待健康检查通过好处:
- 避免后端启动时数据库未就绪导致连接失败
- 确保服务启动顺序正确
- 生产环境更可靠
3. 数据持久化
命名卷(开发环境):
volumes:
postgres_data: # Docker 管理的卷
redis_data:绑定挂载(生产环境):
volumes:
- /data/postgres:/var/lib/postgresql/data # 宿主机路径4. 多阶段构建
优势:
- 构建环境统一(Node 版本、依赖版本完全一致)
- 最终镜像小(只包含 Nginx + dist,约 40MB)
- 安全性高(构建工具不在生产镜像中)
- 避免"在我机器上能跑"的问题
5. 环境变量管理
开发环境:
env_file:
- .env.development # 开发配置生产环境:
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL} # 从宿主机传入性能优化
1. macOS 文件同步优化
volumes:
- .:/app:cached # 添加 cached 标志2. node_modules 隔离(重要)
问题场景:
如果只挂载代码目录,会导致宿主机和容器的 node_modules 互相覆盖:
# ❌ 错误示例
volumes:
- .:/app # 宿主机的 node_modules 会覆盖容器内的问题:
宿主机 (macOS/Windows)
├── node_modules/
│ └── esbuild (macOS 二进制) ❌ 无法在 Linux 容器中运行
└── src/
容器 (Linux Alpine)
├── node_modules/
│ └── esbuild (Linux 二进制) ✅ 应该使用这个
└── src/- 平台不兼容:宿主机(macOS/Windows)的原生模块(esbuild、swc)无法在 Linux 容器中运行
- 覆盖问题:挂载
.:/app会把宿主机的 node_modules 覆盖容器内的 - 启动失败:
Error: The module was compiled against a different Node.js version
解决方案:单独挂载 node_modules
以 Bush 项目为例:
# ✅ 正确示例
services:
bush_dev:
container_name: bush_app_dev
volumes:
# 代码挂载:实时同步代码变更
- .:/app
# node_modules 单独挂载:避免宿主机和容器冲突
- bush_node_modules:/app/node_modules
volumes:
bush_node_modules: # Docker 命名卷,存储容器的 node_modules效果:
宿主机目录
├── node_modules/ (macOS 版本,保留)
├── src/ ✅ 同步到容器
└── package.json ✅ 同步到容器
容器内目录
├── node_modules/ (Linux 版本,独立存储在 Docker Volume)
├── src/ ✅ 从宿主机同步
└── package.json ✅ 从宿主机同步优势:
- ✅ 平台隔离:宿主机和容器的 node_modules 完全独立,各自使用匹配平台的二进制
- ✅ 代码同步:代码修改实时同步,热更新正常工作
- ✅ 开发体验:宿主机可以继续使用 VS Code 智能提示(基于本地 node_modules)
- ✅ 启动速度:依赖缓存在 Docker Volume 中,重启容器无需重新安装
验证:
# 进入容器
docker compose -f docker-compose.dev.yml exec bush_dev sh
# 查看 node_modules 位置
ls -la /app/node_modules/.bin/esbuild
# 输出:Linux x86_64 二进制文件
# 在宿主机查看
file node_modules/.bin/esbuild
# 输出:Mach-O 64-bit executable arm64 (macOS)注意事项:
- 当更新 package.json 后,需要重新构建容器:
docker compose up --build - 清理 Volume:
docker compose down -v(会删除 node_modules,下次启动重新安装)
3. 镜像加速
RUN npm config set registry https://registry.npmmirror.com常见问题排查
1. 容器访问不到后端
检查网络:
docker network ls | grep jungle_default
docker network inspect jungle_default确认容器在同一网络:
docker inspect bush_app_dev | grep NetworkMode
docker inspect jungle_app_dev | grep NetworkMode2. 数据库连接失败
检查健康状态:
docker ps # 查看 STATUS 列
docker logs jungle_postgres_dev手动测试连接:
docker exec -it jungle_postgres_dev psql -U postgres -d jungle3. 热更新不生效
重新构建容器:
docker compose -f docker-compose.dev.yml down
docker compose -f docker-compose.dev.yml up --build总结
架构优势:
- ✅ 环境完全一致(开发/测试/生产)
- ✅ 快速上手(只需 Docker)
- ✅ 服务隔离(数据库、缓存、应用)
- ✅ 易于扩展(添加新服务只需修改 compose 文件)
核心原则:
- 一次配置,处处运行(Build once, run anywhere)
- 依赖显式声明(健康检查、启动顺序)
- 数据持久化(命名卷 vs 绑定挂载)
- 安全优先(最小化镜像、环境变量管理)
待整理
交互式命令行(进入一个运行中的容器):docker exec -it <container_name> sh