DevOps Engineering

Docker 实践指南:从入门到生产部署

容器化不是魔法,而是一套工程实践。分享我 5 年来使用 Docker 的经验教训,包括镜像优化、多阶段构建、安全加固和 CI/CD 集成。

Ioodu · · Updated: Feb 13, 2026 · 24 min read
#Docker #Container #DevOps #Deployment #Infrastructure

为什么需要 Docker

2020 年,我经历了一次典型的「在我机器上可以运行」事故:

代码在本地测试通过,部署到生产环境后崩溃。排查 6 小时后发现问题:开发环境用 Node.js 14,生产环境是 Node.js 12,一个语法特性不支持。

这种环境不一致问题,Docker 可以彻底解决。

Docker 核心概念

镜像(Image)

定义:只读模板,包含运行应用所需的一切(代码、运行时、库、环境变量)。

类比:类(Class)

容器(Container)

定义:镜像的运行实例,是隔离的进程。

类比:对象(Object)

Dockerfile

定义:构建镜像的脚本,包含一系列指令。

# 基础镜像
FROM node:18-alpine

# 工作目录
WORKDIR /app

# 复制依赖文件
COPY package*.json ./

# 安装依赖
RUN npm ci --only=production

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 3000

# 启动命令
CMD ["node", "server.js"]

镜像优化:从 1GB 到 100MB

问题:镜像过大

初始 Dockerfile

FROM ubuntu:latest

RUN apt-get update && apt-get install -y nodejs npm

COPY . /app
WORKDIR /app

RUN npm install

CMD ["node", "server.js"]

问题

  • 使用完整 Ubuntu(1GB+)
  • 包含不必要的包
  • 层数过多

结果:镜像 1.2GB,构建 5 分钟

优化 1:使用精简基础镜像

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

CMD ["node", "server.js"]

改进

  • Alpine Linux:只有 5MB
  • 只安装生产依赖

结果:镜像 180MB,构建 1 分钟

优化 2:多阶段构建

# 构建阶段
FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# 生产阶段
FROM node:18-alpine

WORKDIR /app

# 只复制生产依赖和构建产物
COPY package*.json ./
RUN npm ci --only=production

COPY --from=builder /app/dist ./dist

EXPOSE 3000

CMD ["node", "dist/server.js"]

改进

  • 构建工具和 devDependencies 不进入最终镜像
  • 源码不进入最终镜像(只用构建产物)

结果:镜像 120MB

优化 3:.dockerignore

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.vscode
.idea
tests
*.test.js
*.spec.js

效果:减少构建上下文,加快构建速度

优化 4:层缓存优化

Docker 使用层缓存,合理利用可以加速构建:

# ❌ 不好的做法:每次代码变更都重新安装依赖
COPY . .
RUN npm ci

# ✅ 好的做法:依赖文件不变时不重新安装
COPY package*.json ./
RUN npm ci --only=production
COPY . .

原理

  • package*.json 不常变更 → 缓存命中
  • 代码常变更 → 在依赖层之后 COPY

优化 5:安全加固

FROM node:18-alpine

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

WORKDIR /app

# 设置正确的权限
COPY --chown=nextjs:nodejs package*.json ./
RUN npm ci --only=production

COPY --chown=nextjs:nodejs . .

# 切换到非 root 用户
USER nextjs

EXPOSE 3000

CMD ["node", "server.js"]

安全要点

  • 不使用 root 运行应用
  • 最小权限原则
  • 使用官方镜像
  • 定期更新基础镜像

Docker Compose:多容器管理

开发环境配置

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
    volumes:
      - .:/app
      - /app/node_modules
    depends_on:
      - db
      - redis
    command: npm run dev

  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  # 开发工具
  pgadmin:
    image: dpage/pgadmin4
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: admin
    ports:
      - "5050:80"
    depends_on:
      - db

volumes:
  postgres_data:

生产环境配置

# docker-compose.prod.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.prod
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - app
    restart: unless-stopped

  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 1G

volumes:
  postgres_data:

环境变量管理

# .env (不提交到 git)
DATABASE_URL=postgres://user:pass@localhost:5432/myapp
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-secret-key
# docker-compose.yml
services:
  app:
    env_file:
      - .env
    environment:
      # 覆盖或添加变量
      NODE_ENV: production

CI/CD 集成

GitHub Actions 工作流

# .github/workflows/docker.yml
name: Docker Build and Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Login to Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: |
            ghcr.io/${{ github.repository }}:latest
            ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          # 多阶段构建优化
          target: production

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /opt/myapp
            docker-compose pull
            docker-compose up -d
            docker system prune -f

多架构构建

- name: Build and push multi-arch
  uses: docker/build-push-action@v4
  with:
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ghcr.io/${{ github.repository }}:latest

生产环境最佳实践

1. 健康检查

# Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1
// server.js
app.get('/health', (req, res) => {
  // 检查数据库连接
  // 检查关键依赖
  res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});

2. 日志管理

# docker-compose.yml
services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

或使用集中式日志:

services:
  app:
    logging:
      driver: fluentd
      options:
        fluentd-address: localhost:24224
        tag: docker.app

3. 监控

# docker-compose.monitoring.yml
version: '3.8'

services:
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana
    ports:
      - "3001:3000"
    volumes:
      - grafana_data:/var/lib/grafana

  cadvisor:
    image: gcr.io/cadvisor/cadvisor
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8080:8080"

volumes:
  grafana_data:

4. 备份策略

#!/bin/bash
# backup.sh

# 数据库备份
docker exec postgres pg_dump -U user myapp > backup_$(date +%Y%m%d).sql

# 上传到 S3
aws s3 cp backup_$(date +%Y%m%d).sql s3://myapp-backups/

# 保留最近 7 天
docker exec postgres find /backups -name "backup_*.sql" -mtime +7 -delete
# 定时备份
cron:
  image: mcuadros/ofelia:latest
  command: daemon --docker
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro
  labels:
    ofelia.job-run.backup.schedule: "0 2 * * *"
    ofelia.job-run.backup.container: "postgres"
    ofelia.job-run.backup.command: "/backup.sh"

安全加固

镜像安全扫描

# 使用 Trivy 扫描镜像
trivy image myapp:latest

# 集成到 CI
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest

运行时安全

# docker-compose.yml
services:
  app:
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    read_only: true
    tmpfs:
      - /tmp
      - /var/tmp

网络隔离

# docker-compose.yml
services:
  app:
    networks:
      - frontend
      - backend

  db:
    networks:
      - backend
    # 数据库不暴露到外部

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # 无外部访问

调试技巧

进入运行中的容器

# 交互式 shell
docker exec -it container_name sh

# 查看日志
docker logs -f container_name

# 查看资源使用
docker stats

调试构建

# 查看构建层
docker history myapp:latest

# 检查镜像内容
docker run --rm -it myapp:latest sh

# 导出文件系统
docker export container_name -o filesystem.tar

网络调试

# 查看网络
docker network ls
docker network inspect bridge

# 测试连通性
docker run --rm busybox ping db

常见陷阱

陷阱 1:在容器内保存数据

错误

// 数据保存在容器内
fs.writeFileSync('/app/data.json', data);

问题:容器重启后数据丢失

解决

services:
  app:
    volumes:
      - app_data:/app/data

陷阱 2:忽视信号处理

问题:容器无法优雅关闭

解决

// 正确处理 SIGTERM
process.on('SIGTERM', () => {
  console.log('SIGTERM received, closing gracefully');
  server.close(() => {
    process.exit(0);
  });
});

或使用 dumb-init

RUN apk add --no-cache dumb-init
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]

陷阱 3:时区问题

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

陷阱 4:DNS 缓存

某些基础镜像有 DNS 缓存问题,导致无法解析新的服务名。

解决:使用 nscd 或重启容器。

工具推荐

开发工具

  • Docker Desktop:本地开发(Mac/Windows)
  • Lazydocker:终端 UI 管理
  • ctop:容器资源监控

CI/CD

  • GitHub Actions:与代码仓库集成
  • GitLab CI:一体化 DevOps
  • Jenkins:企业级 CI/CD

安全

  • Trivy:镜像漏洞扫描
  • Clair:容器安全分析
  • Anchore:企业级安全平台

编排

  • Docker Swarm:简单编排
  • Kubernetes:复杂场景
  • Nomad:轻量级替代

总结:Docker 检查清单

开发阶段

  • 使用多阶段构建
  • 优化 .dockerignore
  • 使用非 root 用户
  • 层缓存优化
  • 本地开发用 docker-compose

构建阶段

  • 自动化构建(CI/CD)
  • 镜像安全扫描
  • 版本标签管理(latest + sha)
  • 多架构支持(amd64/arm64)

部署阶段

  • 健康检查配置
  • 资源限制(CPU/内存)
  • 日志管理
  • 监控告警
  • 备份策略
  • 网络隔离

运维阶段

  • 定期更新基础镜像
  • 监控资源使用
  • 定期安全扫描
  • 灾难恢复演练

参考资源

官方文档

书籍

  • 《Docker Deep Dive》Nigel Poulton
  • 《The Docker Book》James Turnbull

课程

  • Docker Mastery (Udemy)
  • Container Fundamentals (Pluralsight)

你使用 Docker 遇到过什么问题?有什么优化技巧?欢迎分享。

本文的示例都经过生产环境验证,运行在全球多个数据中心。

---

评论