返回博客
技术 2025年2月19日 5 分钟阅读 · 1045 字

Docker 容器化部署实战:从 Dockerfile 到多环境编排

从零构建 PHP + Nginx + MySQL 的 Docker 部署方案,涵盖多阶段构建、Compose 编排与健康检查。

#Docker #DevOps #部署 #容器化 #Nginx
本文由 AI 辅助生成,经人工审核发布

为什么用 Docker

传统部署的问题:

  • 「在我机器上能跑」——环境不一致
  • PHP 版本冲突、扩展缺失、配置文件散落各处
  • 回滚困难,需要手动清理

Docker 的解决方案:

  • 镜像即环境,一次构建到处运行
  • 版本控制:每个镜像有 tag,回滚只需切换 tag
  • 隔离性:应用之间互不影响

多阶段 Dockerfile

# === 阶段 1:安装依赖 ===
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --optimize-autoloader
COPY . .
RUN composer run-scripts post-autoload-dump

# === 阶段 2:构建前端资源 ===
FROM node:20-alpine AS frontend
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# === 阶段 3:最终镜像 ===
FROM php:8.4-fpm-alpine

# 安装系统依赖
RUN apk add --no-cache \
    nginx \
    libzip-dev \
    libpng-dev \
    postgresql-dev \
    && docker-php-ext-install pdo_mysql pdo_pgsql zip gd opcache

# 配置 OPcache
RUN echo "[opcache]\n\
opcache.enable=1\n\
opcache.memory_consumption=256\n\
opcache.max_accelerated_files=20000\n\
opcache.validate_timestamps=0" > /usr/local/etc/php/conf.d/opcache.ini

# 复制应用代码
WORKDIR /var/www/html
COPY --from=vendor /app ./
COPY --from=frontend /app/public/build ./public/build

# 复制 Nginx 配置
COPY docker/nginx.conf /etc/nginx/http.d/default.conf

# 设置权限
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

EXPOSE 80
CMD ["sh", "-c", "php-fpm -D && nginx -g 'daemon off;'"]

多阶段构建的优势

# 最终镜像只包含阶段 3 的内容
# composer 和 node 不在最终镜像中
# 镜像大小从 1.2GB 降到 280MB

Nginx 配置

server {
    listen 80;
    server_name _;
    root /var/www/html/public;
    index index.php;

    # 安全:禁止访问隐藏文件
    location ~ /\. {
        deny all;
    }

    # Laravel 路由
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP-FPM
    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # 静态资源缓存
    location ~* \.(js|css|png|jpg|webp|woff2|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 健康检查端点
    location /health {
        access_log off;
        return 200 "ok";
        add_header Content-Type text/plain;
    }
}

Docker Compose 编排

# docker-compose.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    volumes:
      - ./storage:/var/www/html/storage
    environment:
      - DB_HOST=mysql
      - DB_DATABASE=${DB_NAME}
      - DB_USERNAME=${DB_USER}
      - DB_PASSWORD=${DB_PASS}
    depends_on:
      mysql:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped

  mysql:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASS}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    restart: unless-stopped

volumes:
  mysql_data:
  redis_data:

环境分离

# docker-compose.yml(基础配置)
# docker-compose.prod.yml(生产覆盖)
services:
  app:
    environment:
      - APP_ENV=production
      - APP_DEBUG=false
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 512M
          cpus: '1.0'

  mysql:
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
    volumes:
      - ./mysql/prod.cnf:/etc/mysql/conf.d/prod.cnf:ro
# 开发环境
docker compose up -d

# 生产环境
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

.dockerignore

.git
.gitignore
node_modules
vendor
storage/logs/*
storage/framework/cache/*
.env
docker-compose*.yml
Dockerfile*
README.md
tests

.dockerignore 防止不必要文件进入构建上下文,加速构建。

健康检查

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD curl -f http://localhost/health || exit 1
# 查看健康状态
docker inspect --format='{{.State.Health.Status}}' container_name

Docker Compose 中 depends_on + condition: service_healthy 确保 MySQL 就绪后 app 才启动。

镜像优化技巧

1. 使用 Alpine 基础镜像

FROM php:8.4-fpm-alpine  # ~80MB
# vs
FROM php:8.4-fpm          # ~400MB

2. 合并 RUN 指令

# ❌ 每条 RUN 创建一层
RUN apk add nginx
RUN apk add libzip-dev
RUN docker-php-ext-install zip

# ✅ 合并为一条
RUN apk add --no-cache nginx libzip-dev \
    && docker-php-ext-install zip

3. 利用构建缓存

# 先复制不常变化的文件
COPY composer.json composer.lock ./
RUN composer install

# 再复制经常变化的源码
COPY . .

Docker 逐层缓存,composer install 只在 composer.json 变化时重新执行。

CI/CD 集成

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      
      - name: Run tests
        run: docker run --rm myapp:${{ github.sha }} php artisan test
      
      - name: Push to registry
        run: |
          docker tag myapp:${{ github.sha }} registry.example.com/myapp:latest
          docker push registry.example.com/myapp:latest
      
      - name: Deploy
        run: |
          ssh server "cd /app && docker compose pull && docker compose up -d"

总结

Docker 容器化的核心价值是环境一致性和可重复性:

  1. 多阶段构建减小镜像体积
  2. Compose 编排管理多服务依赖
  3. 健康检查确保服务就绪后再启动
  4. 环境分离用 override 文件管理差异
  5. CI/CD 集成实现自动化构建部署
  6. Alpine + 合并 RUN 优化镜像大小