Docker 与 Docker Compose 完整教程:从基础到 Next.js 应用部署
•Devin
DockerDocker Compose
全面介绍 Docker 容器化技术,包括核心概念、常用命令、Dockerfile 编写、多阶段构建、Docker Compose 编排,以及 Next.js 应用的生产环境部署实践
Docker 与 Docker Compose 完整教程:从基础到 Next.js 应用部署
Docker 已经成为现代应用开发和部署的标准工具。本教程将从 Docker 基础概念开始,逐步深入到 Next.js 应用的容器化部署实践。
目录
Docker 核心概念
什么是 Docker?
Docker 是一个开源的容器化平台,允许开发者将应用及其依赖打包到轻量级、可移植的容器中。
核心组件
Preparing diagram...
1. Container (容器)
容器是镜像的运行实例,包含应用运行所需的一切:
- 代码
- 运行时环境
- 系统工具
- 系统库
- 配置文件
特点:
- 轻量级 - 共享主机操作系统内核
- 隔离性 - 每个容器独立运行
- 可移植性 - 一次构建,到处运行
2. Image (镜像)
镜像是创建容器的只读模板,包含多层文件系统:
Ubuntu 基础层
│
├─ Node.js 层
│ │
│ ├─ 应用依赖层
│ │ │
│ │ └─ 应用代码层
镜像特点:
- 分层存储 - 每层可以被复用
- 不可变性 - 镜像一旦创建不可修改
- 版本控制 - 通过 tag 管理不同版本
3. Registry (仓库)
仓库用于存储和分发镜像:
- Docker Hub - 官方公共仓库
- 私有仓库 - 企业内部使用
- 云服务商 - AWS ECR、GCP GCR、Azure ACR
4. Dockerfile
文本文件,包含构建镜像的指令:
FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . CMD ["npm", "start"]
Docker 基础命令
镜像管理
# 搜索镜像 docker search nginx # 拉取镜像 docker pull nginx:latest docker pull node:20-alpine # 列出本地镜像 docker images docker image ls # 查看镜像详细信息 docker inspect nginx:latest # 删除镜像 docker rmi nginx:latest docker image rm nginx:latest # 清理未使用的镜像 docker image prune -a # 构建镜像 docker build -t my-app:1.0 . docker build -t my-app:latest --no-cache . # 标记镜像 docker tag my-app:1.0 username/my-app:1.0 # 推送镜像到仓库 docker push username/my-app:1.0
容器管理
# 运行容器 docker run nginx docker run -d nginx # 后台运行 docker run -d -p 8080:80 nginx # 端口映射 docker run -d --name my-nginx nginx # 指定名称 docker run -d -v /data:/data nginx # 挂载数据卷 # 列出容器 docker ps # 运行中的容器 docker ps -a # 所有容器 docker ps -q # 只显示容器 ID # 查看容器日志 docker logs my-nginx docker logs -f my-nginx # 实时跟踪 docker logs --tail 100 my-nginx # 最后 100 行 # 进入容器 docker exec -it my-nginx bash docker exec -it my-nginx sh # 停止容器 docker stop my-nginx docker stop $(docker ps -q) # 停止所有运行的容器 # 启动容器 docker start my-nginx # 重启容器 docker restart my-nginx # 删除容器 docker rm my-nginx docker rm -f my-nginx # 强制删除 docker rm $(docker ps -aq) # 删除所有容器 # 查看容器资源使用 docker stats docker stats my-nginx # 查看容器详细信息 docker inspect my-nginx
网络管理
# 列出网络 docker network ls # 创建网络 docker network create my-network # 查看网络详情 docker network inspect my-network # 连接容器到网络 docker network connect my-network my-nginx # 断开网络连接 docker network disconnect my-network my-nginx # 删除网络 docker network rm my-network
数据卷管理
# 列出数据卷 docker volume ls # 创建数据卷 docker volume create my-data # 查看数据卷详情 docker volume inspect my-data # 删除数据卷 docker volume rm my-data # 清理未使用的数据卷 docker volume prune
系统管理
# 查看 Docker 信息 docker info # 查看 Docker 版本 docker version # 清理系统 docker system prune # 清理未使用的资源 docker system prune -a # 清理所有未使用的资源 docker system prune --volumes # 包括数据卷 # 查看磁盘使用情况 docker system df
编写 Dockerfile
Dockerfile 基础指令
# FROM - 指定基础镜像 FROM node:20-alpine # LABEL - 添加元数据 LABEL maintainer="your-email@example.com" LABEL version="1.0" LABEL description="My Next.js Application" # WORKDIR - 设置工作目录 WORKDIR /app # COPY - 复制文件 COPY package*.json ./ COPY . . # ADD - 复制并解压(支持 URL 和自动解压) ADD https://example.com/file.tar.gz /tmp/ ADD archive.tar.gz /app/ # RUN - 执行命令(构建时) RUN npm install RUN npm run build # ENV - 设置环境变量 ENV NODE_ENV=production ENV PORT=3000 # ARG - 构建参数 ARG NODE_VERSION=20 FROM node:${NODE_VERSION}-alpine # EXPOSE - 声明端口 EXPOSE 3000 # VOLUME - 声明数据卷 VOLUME ["/data"] # USER - 指定运行用户 USER node # CMD - 容器启动命令(可被覆盖) CMD ["npm", "start"] # ENTRYPOINT - 容器入口点(不可轻易覆盖) ENTRYPOINT ["npm"] CMD ["start"] # 作为 ENTRYPOINT 的默认参数
简单示例
# 基础 Node.js 应用 FROM node:20-alpine # 设置工作目录 WORKDIR /app # 复制依赖文件 COPY package*.json ./ # 安装依赖 RUN npm ci --only=production # 复制应用代码 COPY . . # 暴露端口 EXPOSE 3000 # 启动应用 CMD ["node", "server.js"]
.dockerignore 文件
类似 .gitignore,用于排除不需要复制到镜像的文件:
# 依赖目录 node_modules npm-debug.log yarn-error.log .pnpm-debug.log # Next.js .next/ out/ build/ dist/ # 开发文件 .git .gitignore .env*.local *.md .vscode .idea # 测试文件 coverage/ .nyc_output/ *.test.js *.spec.js # 系统文件 .DS_Store Thumbs.db # CI/CD .github/ .gitlab-ci.yml
多阶段构建
多阶段构建允许在一个 Dockerfile 中使用多个 FROM 指令,减小最终镜像大小。
基本概念
Preparing diagram...
简单示例
# 阶段 1: 构建阶段 FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 阶段 2: 生产阶段 FROM node:20-alpine AS production WORKDIR /app # 只复制必要的文件 COPY --from=builder /app/package*.json ./ COPY --from=builder /app/dist ./dist RUN npm ci --only=production EXPOSE 3000 CMD ["node", "dist/server.js"]
Next.js 多阶段构建
# 阶段 1: 依赖安装 FROM node:20-alpine AS deps WORKDIR /app # 复制依赖文件 COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ # 根据包管理器安装依赖 RUN \ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ else echo "Lockfile not found." && exit 1; \ fi # 阶段 2: 构建阶段 FROM node:20-alpine AS builder WORKDIR /app # 复制依赖 COPY --from=deps /app/node_modules ./node_modules COPY . . # 设置环境变量 ENV NEXT_TELEMETRY_DISABLED=1 # 构建应用 RUN \ if [ -f yarn.lock ]; then yarn build; \ elif [ -f package-lock.json ]; then npm run build; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm build; \ else echo "Lockfile not found." && exit 1; \ fi # 阶段 3: 生产运行 FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 # 创建非 root 用户 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # 复制必要的文件 COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" CMD ["node", "server.js"]
多阶段构建优势
- 减小镜像体积 - 只包含运行时必需的文件
- 提高安全性 - 不包含构建工具和源代码
- 缓存优化 - 依赖层可以被缓存复用
- 清晰分离 - 构建环境和运行环境分离
镜像大小对比
# 单阶段构建 node:20-alpine + 源代码 + node_modules + 构建工具 ≈ 1.2 GB # 多阶段构建 node:20-alpine + 构建产物 + 生产依赖 ≈ 200 MB
Docker Compose 编排
Docker Compose 是用于定义和运行多容器 Docker 应用的工具。
基本概念
Preparing diagram...
基础示例
# docker-compose.yml version: '3.8' services: # Web 服务 web: build: . ports: - "3000:3000" environment: - NODE_ENV=production depends_on: - db - redis # 数据库服务 db: image: postgres:16-alpine environment: POSTGRES_USER: myuser POSTGRES_PASSWORD: mypassword POSTGRES_DB: mydb volumes: - db-data:/var/lib/postgresql/data ports: - "5432:5432" # Redis 缓存 redis: image: redis:7-alpine ports: - "6379:6379" volumes: db-data:
Next.js 完整示例
version: '3.8' services: # Next.js 应用 nextjs: build: context: . dockerfile: Dockerfile args: - NODE_VERSION=20 container_name: nextjs-app restart: unless-stopped ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=postgresql://postgres:password@postgres:5432/mydb - REDIS_URL=redis://redis:6379 depends_on: postgres: condition: service_healthy redis: condition: service_started networks: - app-network volumes: - ./logs:/app/logs # PostgreSQL 数据库 postgres: image: postgres:16-alpine container_name: postgres-db restart: unless-stopped environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_DB: mydb ports: - "5432:5432" volumes: - postgres-data:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql networks: - app-network healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 # Redis 缓存 redis: image: redis:7-alpine container_name: redis-cache restart: unless-stopped command: redis-server --appendonly yes ports: - "6379:6379" volumes: - redis-data:/data networks: - app-network # Nginx 反向代理(可选) nginx: image: nginx:alpine container_name: nginx-proxy restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro depends_on: - nextjs networks: - app-network networks: app-network: driver: bridge volumes: postgres-data: redis-data:
Docker Compose 命令
# 启动所有服务 docker-compose up docker-compose up -d # 后台运行 # 启动特定服务 docker-compose up nextjs postgres # 构建镜像 docker-compose build docker-compose build --no-cache # 不使用缓存 # 查看服务状态 docker-compose ps # 查看日志 docker-compose logs docker-compose logs -f nextjs # 实时跟踪特定服务 docker-compose logs --tail=100 # 最后 100 行 # 停止服务 docker-compose stop docker-compose stop nextjs # 停止特定服务 # 停止并删除容器 docker-compose down docker-compose down -v # 同时删除数据卷 docker-compose down --rmi all # 同时删除镜像 # 重启服务 docker-compose restart docker-compose restart nextjs # 执行命令 docker-compose exec nextjs sh docker-compose exec postgres psql -U postgres # 查看配置 docker-compose config # 拉取镜像 docker-compose pull # 扩展服务 docker-compose up --scale nextjs=3
环境变量管理
# docker-compose.yml services: nextjs: env_file: - .env - .env.production environment: - NODE_ENV=production - PORT=3000
# .env 文件 DATABASE_URL=postgresql://user:pass@localhost:5432/db REDIS_URL=redis://localhost:6379 NEXT_PUBLIC_API_URL=https://api.example.com SECRET_KEY=your-secret-key
Next.js 应用容器化
配置 Next.js
首先,需要在 next.config.js 中启用 standalone 输出模式:
// next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { // 启用 standalone 输出 output: 'standalone', // 其他配置 reactStrictMode: true, swcMinify: true, } module.exports = nextConfig
完整 Dockerfile
# 依赖安装阶段 FROM node:20-alpine AS deps # 检查 libc 兼容性 RUN apk add --no-cache libc6-compat WORKDIR /app # 复制依赖文件 COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ # 根据锁文件安装依赖 RUN \ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ else echo "Lockfile not found." && exit 1; \ fi # 构建阶段 FROM node:20-alpine AS builder WORKDIR /app # 复制依赖 COPY --from=deps /app/node_modules ./node_modules COPY . . # 禁用遥测 ENV NEXT_TELEMETRY_DISABLED=1 # 构建参数 ARG NEXT_PUBLIC_API_URL ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL # 构建应用 RUN \ if [ -f yarn.lock ]; then yarn build; \ elif [ -f package-lock.json ]; then npm run build; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm build; \ else echo "Lockfile not found." && exit 1; \ fi # 生产运行阶段 FROM node:20-alpine AS runner WORKDIR /app # 设置环境变量 ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 # 创建非 root 用户 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # 复制必要文件 COPY --from=builder /app/public ./public # 复制 standalone 输出 COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # 切换到非 root 用户 USER nextjs # 暴露端口 EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" # 启动应用 CMD ["node", "server.js"]
构建和运行
# 构建镜像 docker build -t nextjs-app . # 使用构建参数 docker build \ --build-arg NEXT_PUBLIC_API_URL=https://api.example.com \ -t nextjs-app:1.0 . # 运行容器 docker run -d \ --name nextjs \ -p 3000:3000 \ -e DATABASE_URL=postgresql://user:pass@host:5432/db \ nextjs-app:1.0 # 查看日志 docker logs -f nextjs # 停止容器 docker stop nextjs
使用 Docker Compose
# docker-compose.yml version: '3.8' services: nextjs: build: context: . dockerfile: Dockerfile args: NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL} ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=${DATABASE_URL} restart: unless-stopped postgres: image: postgres:16-alpine environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - postgres-data:/var/lib/postgresql/data ports: - "5432:5432" volumes: postgres-data:
# 启动服务 docker-compose up -d # 查看日志 docker-compose logs -f # 停止服务 docker-compose down
生产环境最佳实践
1. 镜像优化
使用 Alpine 镜像
# 推荐: 使用 Alpine 版本(体积更小) FROM node:20-alpine # 避免: 使用完整版本 FROM node:20
多阶段构建
# 只复制必要的构建产物 COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/public ./public
合并 RUN 指令
# 推荐: 合并命令减少层数 RUN apk add --no-cache libc6-compat && \ npm ci --only=production && \ npm cache clean --force # 避免: 多个 RUN 指令 RUN apk add --no-cache libc6-compat RUN npm ci --only=production RUN npm cache clean --force
2. 安全实践
使用非 root 用户
# 创建专用用户 RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs # 切换用户 USER nextjs # 确保文件权限正确 COPY --chown=nextjs:nodejs /app/.next ./
扫描漏洞
# 使用 Docker Scout 扫描 docker scout cves nextjs-app:latest # 使用 Trivy 扫描 trivy image nextjs-app:latest
最小化攻击面
# 只安装生产依赖 RUN npm ci --only=production # 移除不必要的文件 RUN rm -rf /tmp/* /var/cache/apk/*
3. 健康检查
# 添加健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# docker-compose.yml 中的健康检查 services: nextjs: healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] interval: 30s timeout: 10s retries: 3 start_period: 40s
4. 资源限制
services: nextjs: deploy: resources: limits: cpus: '1.0' memory: 1G reservations: cpus: '0.5' memory: 512M restart: unless-stopped
5. 日志管理
services: nextjs: logging: driver: "json-file" options: max-size: "10m" max-file: "3"
# 查看日志 docker-compose logs -f --tail=100 nextjs # 清理日志 docker-compose down && docker-compose up -d
6. 环境变量安全
# 使用 secrets(Docker Swarm) secrets: db_password: external: true services: nextjs: secrets: - db_password
# 从文件加载敏感信息 echo "my-secret-password" | docker secret create db_password -
7. 网络安全
networks: # 前端网络 frontend: driver: bridge # 后端网络(隔离数据库) backend: driver: bridge internal: true # 禁止外部访问 services: nextjs: networks: - frontend - backend postgres: networks: - backend # 只在后端网络
8. 备份策略
# 备份数据卷 docker run --rm \ -v postgres-data:/data \ -v $(pwd):/backup \ alpine tar czf /backup/postgres-backup-$(date +%Y%m%d).tar.gz /data # 恢复数据卷 docker run --rm \ -v postgres-data:/data \ -v $(pwd):/backup \ alpine tar xzf /backup/postgres-backup-20250126.tar.gz -C /
常见问题与排查
1. 构建问题
问题:构建很慢
# 解决方案: 优化层缓存 # 推荐: 先复制依赖文件 COPY package*.json ./ RUN npm ci # 再复制源代码 COPY . . RUN npm run build # 避免: 一次性复制所有文件 COPY . . RUN npm ci && npm run build
问题:node_modules 太大
# 确保 .dockerignore 排除 node_modules node_modules .next .git
2. 运行问题
问题:容器启动后立即退出
# 查看日志 docker logs nextjs # 常见原因: # 1. CMD 命令错误 # 2. 端口冲突 # 3. 环境变量缺失
问题:无法访问服务
# 检查端口映射 docker ps # 检查容器内部 docker exec -it nextjs sh curl http://localhost:3000 # 检查防火墙 sudo ufw status
3. 网络问题
问题:容器间无法通信
# 确保在同一网络 services: nextjs: networks: - app-network postgres: networks: - app-network networks: app-network:
# 检查网络连接 docker-compose exec nextjs ping postgres
4. 性能问题
问题:Mac/Windows 上文件 I/O 慢
# 使用命名卷而不是绑定挂载 volumes: - node_modules:/app/node_modules # 而不是 volumes: - ./node_modules:/app/node_modules
问题:内存不足
# 增加 Docker 内存限制 # Docker Desktop -> Settings -> Resources -> Memory # 或在 docker-compose.yml 中限制 deploy: resources: limits: memory: 2G
5. 调试技巧
# 进入运行中的容器 docker exec -it nextjs sh # 检查环境变量 docker exec nextjs env # 检查进程 docker exec nextjs ps aux # 检查文件 docker exec nextjs ls -la /app # 复制文件出来检查 docker cp nextjs:/app/.next ./debug-next # 查看容器详细信息 docker inspect nextjs # 查看资源使用 docker stats nextjs
6. 清理和维护
# 清理未使用的资源 docker system prune -a # 清理特定类型 docker image prune # 清理镜像 docker container prune # 清理容器 docker volume prune # 清理数据卷 docker network prune # 清理网络 # 查看磁盘使用 docker system df # 查看详细信息 docker system df -v
总结
Docker 核心要点
-
容器化优势
- 环境一致性
- 快速部署
- 资源隔离
- 易于扩展
-
最佳实践
- 使用多阶段构建
- 优化镜像层缓存
- 使用非 root 用户
- 实施健康检查
-
Next.js 特殊配置
- 启用
output: 'standalone' - 正确复制
.next/standalone和.next/static - 配置环境变量
- 使用适当的基础镜像
- 启用
生产部署检查清单
- 启用多阶段构建
- 使用 Alpine 基础镜像
- 配置 .dockerignore
- 使用非 root 用户运行
- 实施健康检查
- 设置资源限制
- 配置日志轮转
- 实施安全扫描
- 配置备份策略
- 设置监控告警
参考资源
通过本教程,你应该能够:
- 理解 Docker 核心概念和工作原理
- 编写优化的 Dockerfile 和 Docker Compose 配置
- 将 Next.js 应用容器化并部署到生产环境
- 排查常见的 Docker 问题
- 遵循生产环境最佳实践
Docker 和容器化技术是现代应用部署的基础设施,掌握这些技能将极大提升你的开发和运维能力。