发布于

Docker容器化实战指南:从开发到生产环境的完整部署

作者

Docker容器化实战指南:从开发到生产环境的完整部署

Docker容器化技术为应用部署提供了标准化、可移植的解决方案。本文将分享Docker在前端和全栈项目中的实战应用经验。

Docker基础概念

Dockerfile最佳实践

# 前端React应用Dockerfile
# 多阶段构建 - 构建阶段
FROM node:18-alpine AS builder

# 设置工作目录
WORKDIR /app

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

# 安装依赖(利用Docker缓存层)
RUN npm ci --only=production && npm cache clean --force

# 复制源代码
COPY . .

# 构建应用
RUN npm run build

# 生产阶段 - 使用nginx服务静态文件
FROM nginx:alpine AS production

# 安装必要工具
RUN apk add --no-cache curl

# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf

# 从构建阶段复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html

# 复制启动脚本
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost/ || exit 1

# 暴露端口
EXPOSE 80

# 设置用户(安全考虑)
USER nginx

# 启动命令
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

# nginx.conf - Nginx配置文件
events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    
    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    
    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log warn;
    
    # 性能优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    
    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml+rss
        application/atom+xml
        image/svg+xml;
    
    server {
        listen 80;
        server_name localhost;
        root /usr/share/nginx/html;
        index index.html;
        
        # 安全头
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "no-referrer-when-downgrade" always;
        add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
        
        # 静态资源缓存
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
        
        # HTML文件不缓存
        location ~* \.html$ {
            expires -1;
            add_header Cache-Control "no-cache, no-store, must-revalidate";
        }
        
        # SPA路由支持
        location / {
            try_files $uri $uri/ /index.html;
        }
        
        # API代理
        location /api/ {
            proxy_pass http://backend:3000/;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }
        
        # 健康检查端点
        location /health {
            access_log off;
            return 200 "healthy\n";
            add_header Content-Type text/plain;
        }
    }
}

# docker-entrypoint.sh - 启动脚本
#!/bin/sh
set -e

# 环境变量替换
if [ -n "$API_URL" ]; then
    echo "Setting API_URL to $API_URL"
    find /usr/share/nginx/html -name "*.js" -exec sed -i "s|__API_URL__|$API_URL|g" {} \;
fi

if [ -n "$APP_TITLE" ]; then
    echo "Setting APP_TITLE to $APP_TITLE"
    find /usr/share/nginx/html -name "*.html" -exec sed -i "s|__APP_TITLE__|$APP_TITLE|g" {} \;
fi

# 执行原始命令
exec "$@"

Node.js后端Dockerfile

# Node.js后端应用Dockerfile
FROM node:18-alpine AS base

# 安装系统依赖
RUN apk add --no-cache \
    dumb-init \
    curl \
    && rm -rf /var/cache/apk/*

# 创建应用用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001

# 设置工作目录
WORKDIR /app

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

# 开发依赖阶段
FROM base AS deps
RUN npm ci --include=dev

# 构建阶段
FROM base AS build
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# 构建应用(如果有TypeScript等需要编译的代码)
RUN npm run build

# 生产依赖阶段
FROM base AS prod-deps
RUN npm ci --only=production && npm cache clean --force

# 生产阶段
FROM base AS production

# 复制生产依赖
COPY --from=prod-deps /app/node_modules ./node_modules

# 复制构建产物
COPY --from=build /app/dist ./dist
COPY --from=build /app/package*.json ./

# 复制其他必要文件
COPY --from=build /app/public ./public

# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:$PORT/health || exit 1

# 暴露端口
EXPOSE $PORT

# 切换到非root用户
USER nodejs

# 使用dumb-init作为PID 1
ENTRYPOINT ["dumb-init", "--"]

# 启动应用
CMD ["node", "dist/server.js"]

# .dockerignore - Docker忽略文件
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output
.coverage
.vscode
.idea
*.log
dist
build
.DS_Store
Thumbs.db

Docker Compose编排

开发环境配置

# docker-compose.dev.yml - 开发环境
version: '3.8'

services:
  # 前端开发服务
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - CHOKIDAR_USEPOLLING=true
      - REACT_APP_API_URL=http://localhost:3001/api
    depends_on:
      - backend
    networks:
      - app-network
    restart: unless-stopped

  # 后端API服务
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    ports:
      - "3001:3000"
      - "9229:9229"  # Node.js调试端口
    volumes:
      - ./backend:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - PORT=3000
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_NAME=myapp_dev
      - DB_USER=postgres
      - DB_PASSWORD=password
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=dev-secret-key
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network
    restart: unless-stopped
    command: npm run dev:debug

  # PostgreSQL数据库
  postgres:
    image: postgres:15-alpine
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=myapp_dev
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network
    restart: unless-stopped

  # Redis缓存
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    networks:
      - app-network
    restart: unless-stopped
    command: redis-server --appendonly yes

  # 数据库管理工具
  adminer:
    image: adminer:latest
    ports:
      - "8080:8080"
    environment:
      - ADMINER_DEFAULT_SERVER=postgres
    depends_on:
      - postgres
    networks:
      - app-network
    restart: unless-stopped

  # 邮件测试服务
  mailhog:
    image: mailhog/mailhog:latest
    ports:
      - "1025:1025"  # SMTP
      - "8025:8025"  # Web UI
    networks:
      - app-network
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

networks:
  app-network:
    driver: bridge

# Dockerfile.dev - 前端开发Dockerfile
FROM node:18-alpine

WORKDIR /app

# 安装依赖
COPY package*.json ./
RUN npm install

# 暴露端口
EXPOSE 3000

# 启动开发服务器
CMD ["npm", "start"]

# Dockerfile.dev - 后端开发Dockerfile
FROM node:18-alpine

# 安装系统依赖
RUN apk add --no-cache curl

WORKDIR /app

# 安装依赖
COPY package*.json ./
RUN npm install

# 暴露端口
EXPOSE 3000 9229

# 启动开发服务器
CMD ["npm", "run", "dev"]

生产环境配置

# docker-compose.prod.yml - 生产环境
version: '3.8'

services:
  # Nginx反向代理
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - nginx_logs:/var/log/nginx
    depends_on:
      - frontend
      - backend
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # 前端应用
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      target: production
    environment:
      - API_URL=https://api.example.com
      - APP_TITLE=My Production App
    networks:
      - app-network
    restart: unless-stopped
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 128M
        reservations:
          memory: 64M

  # 后端API服务
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
      target: production
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_NAME=${DB_NAME}
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
      - EMAIL_HOST=${EMAIL_HOST}
      - EMAIL_USER=${EMAIL_USER}
      - EMAIL_PASS=${EMAIL_PASS}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network
    restart: unless-stopped
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # PostgreSQL数据库
  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=${DB_NAME}
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./database/backup:/backup
    networks:
      - app-network
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis缓存
  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    networks:
      - app-network
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 256M
        reservations:
          memory: 128M
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    command: redis-server --appendonly yes --maxmemory 200mb --maxmemory-policy allkeys-lru

volumes:
  postgres_data:
  redis_data:
  nginx_logs:

networks:
  app-network:
    driver: bridge

# .env.prod - 生产环境变量
DB_NAME=myapp_prod
DB_USER=myapp_user
DB_PASSWORD=secure_password_here
JWT_SECRET=super_secure_jwt_secret_key
EMAIL_HOST=smtp.example.com
EMAIL_USER=noreply@example.com
EMAIL_PASS=email_password_here

部署脚本与CI/CD

部署脚本

#!/bin/bash
# deploy.sh - 部署脚本

set -e

# 配置变量
PROJECT_NAME="myapp"
DOCKER_REGISTRY="your-registry.com"
VERSION=${1:-latest}
ENVIRONMENT=${2:-production}

echo "🚀 Starting deployment of $PROJECT_NAME:$VERSION to $ENVIRONMENT"

# 检查Docker和Docker Compose
if ! command -v docker &> /dev/null; then
    echo "❌ Docker is not installed"
    exit 1
fi

if ! command -v docker-compose &> /dev/null; then
    echo "❌ Docker Compose is not installed"
    exit 1
fi

# 创建必要目录
mkdir -p logs backups

# 备份数据库(生产环境)
if [ "$ENVIRONMENT" = "production" ]; then
    echo "📦 Creating database backup..."
    docker-compose -f docker-compose.prod.yml exec -T postgres pg_dump -U $DB_USER $DB_NAME > "backups/backup-$(date +%Y%m%d-%H%M%S).sql"
fi

# 拉取最新镜像
echo "📥 Pulling latest images..."
docker-compose -f docker-compose.$ENVIRONMENT.yml pull

# 构建镜像
echo "🔨 Building images..."
docker-compose -f docker-compose.$ENVIRONMENT.yml build --no-cache

# 停止旧容器
echo "🛑 Stopping old containers..."
docker-compose -f docker-compose.$ENVIRONMENT.yml down

# 启动新容器
echo "🚀 Starting new containers..."
docker-compose -f docker-compose.$ENVIRONMENT.yml up -d

# 等待服务启动
echo "⏳ Waiting for services to start..."
sleep 30

# 健康检查
echo "🏥 Performing health checks..."
check_service() {
    local service=$1
    local url=$2
    local max_attempts=30
    local attempt=1
    
    while [ $attempt -le $max_attempts ]; do
        if curl -f -s "$url" > /dev/null; then
            echo "✅ $service is healthy"
            return 0
        fi
        
        echo "⏳ Waiting for $service... (attempt $attempt/$max_attempts)"
        sleep 10
        ((attempt++))
    done
    
    echo "❌ $service health check failed"
    return 1
}

# 检查各服务健康状态
check_service "Frontend" "http://localhost/health"
check_service "Backend" "http://localhost/api/health"

# 清理旧镜像
echo "🧹 Cleaning up old images..."
docker image prune -f

# 显示运行状态
echo "📊 Deployment status:"
docker-compose -f docker-compose.$ENVIRONMENT.yml ps

echo "✅ Deployment completed successfully!"
echo "🌐 Application is available at: http://localhost"

# 发送通知(可选)
if [ -n "$SLACK_WEBHOOK" ]; then
    curl -X POST -H 'Content-type: application/json' \
        --data "{\"text\":\"🚀 $PROJECT_NAME:$VERSION deployed to $ENVIRONMENT successfully!\"}" \
        $SLACK_WEBHOOK
fi

总结

Docker容器化的核心要点:

  1. 镜像构建:多阶段构建、层缓存优化、安全最佳实践
  2. 容器编排:Docker Compose、服务依赖、健康检查
  3. 环境管理:开发/生产环境分离、配置管理
  4. 部署自动化:CI/CD流水线、滚动更新、回滚策略
  5. 监控运维:日志收集、性能监控、故障排查

Docker容器化技术为现代应用部署提供了标准化、可扩展的解决方案,是DevOps实践中不可或缺的重要工具。