0

Series Dockerize Laravel Thực chiến #6: Trạm cuối – Tuyệt kỹ Multi-stage Build cho Production

1. Multi-stage Build là gì? Tại sao phải dùng nó?

Hãy tưởng tượng quá trình build một dự án Laravel giống như việc nướng bánh. Để nướng bánh (chạy Laravel), bạn cần bột mì, trứng, máy đánh trứng, lò nướng (đó là Composer, Node.js, git, unzip...). Nhưng khi giao cái bánh cho khách hàng ăn (chạy trên Server Production), bạn không cần phải gói theo cái máy đánh trứng hay cái lò nướng! Bạn chỉ cần giao cái bánh thành phẩm.

Multi-stage Build cho phép bạn định nghĩa nhiều "giai đoạn" (stage) trong cùng một file Dockerfile.

  • Giai đoạn 1 (Lò nướng): Dùng image composer khổng lồ để tải toàn bộ thư viện (vendor).
  • Giai đoạn 2 (Thành phẩm): Dùng image php-fpm-alpine siêu nhẹ. Sau đó, chỉ copy đúng thư mục vendor đã được xử lý xong từ Giai đoạn 1 sang.

Kết quả: Từ một Image nặng 1GB (chứa đủ thứ rác biên dịch), Image Production của bạn chỉ còn chưa tới 150MB!

2. Viết lại Dockerfile cho môi trường Production

Hãy tạo một file mới mang tên docker/php/Dockerfile.prod để chuyên phục vụ cho lúc deploy. Cấu trúc sẽ chia làm 2 giai đoạn rõ rệt:

# ==========================================
# GIAI ĐOẠN 1: Cài đặt thư viện (Vendor)
# ==========================================
FROM composer:2.6 as builder

WORKDIR /app

# Copy các file quản lý thư viện vào trước để tận dụng Docker Cache
COPY composer.json composer.lock ./

# Chạy lệnh install. Bỏ qua các thư viện dev (như phpunit) để giảm dung lượng
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist

# Sau đó mới copy toàn bộ source code vào
COPY . .

# Chạy autoload lại một lần nữa cho chuẩn xác
RUN composer dump-autoload --optimize


# ==========================================
# GIAI ĐOẠN 2: Đóng gói Image Production
# ==========================================
# Sử dụng bản Alpine cực nhẹ của PHP
FROM php:8.2-fpm-alpine

# Cài đặt các extension bắt buộc cho hệ thống (PDO MySQL, Redis...)
RUN set -ex \
  && apk --no-cache add \
    libzip-dev \
    libpng-dev \
  && docker-php-ext-install pdo_mysql gd zip bcmath \
  && apk add --no-cache pcre-dev $PHPIZE_DEPS \
  && pecl install redis \
  && docker-php-ext-enable redis

WORKDIR /var/www/html

# COPY MÃ NGUỒN TỪ GIAI ĐOẠN 1 SANG
COPY --from=builder /app /var/www/html

# Chuyển quyền sở hữu cho user www-data (quan trọng để không bị lỗi Permission)
RUN chown -R www-data:www-data /var/www/html \
    && chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache

# Expose port 9000 cho Nginx gọi vào
EXPOSE 9000

# Chạy PHP-FPM
CMD ["php-fpm"]

3. Tinh chỉnh lại Docker Compose cho Server

Trên server thật, file docker-compose.yml của bạn sẽ rất "gọn gàng". Bạn không cần map volume mã nguồn nữa. Bạn cũng không cần mở port của MySQL hay Redis ra ngoài Internet để tránh bị hacker tấn công, chỉ mở duy nhất port 80/443 của Nginx.

version: '3.8'

services:
  webserver:
    image: nginx:alpine
    container_name: laravel_web_prod
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app
    networks:
      - prod_network

  app:
    build:
      context: .
      dockerfile: docker/php/Dockerfile.prod
    container_name: laravel_app_prod
    restart: always
    environment:
      - APP_ENV=production
      - APP_DEBUG=false
    networks:
      - prod_network

  # Các service db, redis, queue-worker, scheduler giữ nguyên logic nhưng bỏ đi các port mapping ra ngoài.
  # ...

networks:
  prod_network:
    driver: bridge

4. Triển khai lên máy chủ thật

Khi đẩy code lên một con VPS mới tinh tại AWS hay DigitalOcean, quy trình deploy của bạn giờ đây chỉ gồm 2 bước:

  1. Copy file .env.production vào server.
  2. Gõ lệnh:
docker-compose -f docker-compose.prod.yml up -d --build

Hệ thống sẽ tự động tải các Image nhẹ nhất, tự động cài thư viện (mà server không cần có Composer), tự động cấp quyền Đọc/Ghi, và bật toàn bộ hệ thống lên một cách trơn tru, bảo mật. Bạn hoàn toàn có thể nhúng lệnh này vào Jenkins Pipeline để nó tự động chạy mỗi khi có code mới được merge vào nhánh main.

Lời kết cho Hành trình Dockerize Laravel

Chúc mừng bạn! Chúng ta đã hoàn tất một chặng đường dài và cực kỳ chất lượng.

Từ việc xóa bỏ thói quen dùng XAMPP để làm quen với kiến trúc LEMP Stack nguyên bản (Bài 1 & 2), đến việc thuần hóa Artisan và sửa lỗi phân quyền (Bài 3). Chúng ta cũng không quên xây dựng sức mạnh ngầm cho hệ thống với Redis Queue Worker (Bài 4) và tự động hóa vận hành bằng Cronjob Scheduler (Bài 5). Cuối cùng, kỹ thuật Multi-stage Build ở bài học hôm nay đã chốt lại toàn bộ bức tranh kiến trúc Enterprise.

Khi bạn làm chủ được bộ khung này, việc quản lý một ứng dụng có vài chục ngàn lượt truy cập hay việc kết nối các Microservices phức tạp sẽ không còn là nỗi ám ảnh. Máy tính cá nhân của bạn sẽ luôn gọn gàng, và code của bạn tung lên bất kỳ server Linux nào cũng sẽ chạy chính xác 100%.

Chúc bạn giữ vững tư duy hệ thống này và ứng dụng thành công vào những dự án Backend quan trọng sắp tới!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí