Môi Trường Docker (Phần 3): PHP-FPM, Nhiều Database & Dev Tools
Ở Phần 1 chúng ta thiết kế kiến trúc. Ở Phần 2 chúng ta cấu hình Nginx reverse proxy. Bây giờ hãy xây dựng phần backend: PHP-FPM container, database, và dev tools.
PHP-FPM Container
Tại Sao PHP-FPM, Không Phải Apache?
| Apache + mod_php | PHP-FPM | |
|---|---|---|
| Kiến trúc | PHP chạy bên trong Apache | PHP chạy như tiến trình riêng |
| Tài nguyên | Nặng hơn (Apache + PHP mỗi request) | Nhẹ hơn (Nginx serve file tĩnh, PHP chỉ xử lý .php) |
| Mở rộng | Scale = thêm Apache instance | Scale PHP độc lập với web server |
| Nhiều phiên bản | Cần nhiều bản cài Apache | Chỉ cần thêm FPM container |
Trong setup này, Nginx xử lý toàn bộ HTTP (file tĩnh, routing) và ủy thác thực thi PHP cho các FPM container riêng biệt qua FastCGI. Sự tách biệt rõ ràng này là lý do chúng ta chạy được 4 phiên bản PHP không xung đột.
Dockerfile-8.4
FROM php:8.4-fpm
# ─── Hệ thống ───────────────────────────────────────
RUN apt-get update -y
# Múi giờ
RUN apt-get install -y tzdata
ENV TZ Asia/Tokyo
# Công cụ Linux (hữu ích cho debug bên trong container)
RUN apt-get install -y curl htop procps tmux vim cron
# ─── PHP Extensions ─────────────────────────────────
# Thư viện xử lý ảnh
RUN apt-get install -y \
libfreetype-dev \
libjpeg62-turbo-dev \
libpng-dev \
libzip-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd
# Dùng community extension installer cho phần còn lại
RUN curl -sSLf \
-o /usr/local/bin/install-php-extensions \
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
chmod +x /usr/local/bin/install-php-extensions
RUN install-php-extensions \
calendar \
exif \
gd \
imagick \
intl \
memcached \
mysqli \
pdo \
pdo_mysql \
pdo_pgsql \
pdo_sqlite \
redis \
soap \
xdebug \
zip \
ftp \
@composer
# ─── Composer & Laravel ──────────────────────────────
RUN composer global require laravel/installer
# ─── PHP Config ──────────────────────────────────────
COPY ./custom.ini /usr/local/etc/php/conf.d/
# Cho phép file .html được xử lý bởi PHP-FPM
RUN echo "security.limit_extensions = .php .html" >> /usr/local/etc/php-fpm.d/www.conf
Quyết Định Thiết Kế Quan Trọng
Tool install-php-extensions: Thay vì compile thủ công từng extension (rất phiền và dễ lỗi), chúng ta dùng community extension installer. Nó tự xử lý tất cả dependency.
@composer: Prefix @ cài Composer như công cụ global.
security.limit_extensions: Mặc định PHP-FPM chỉ xử lý file .php. Thêm .html cho phép bạn dùng PHP trong file HTML nếu cần.
custom.ini — Cấu Hình PHP
file_uploads = On
upload_max_filesize = 2000M
post_max_size = 2000M
memory_limit = 2000M
max_execution_time = 3000
; Custom error reporting
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED
Đây là cài đặt phù hợp cho môi trường phát triển. Upload lớn và thời gian thực thi dài hoàn toàn ok cho local development (bạn sẽ không bao giờ dùng giá trị này trên production).
Chiến Lược Nhiều Phiên Bản
Mỗi phiên bản PHP có Dockerfile riêng với cùng cấu trúc nhưng khác base image:
Dockerfile-8.4 → FROM php:8.4-fpm
Dockerfile-8.2 → FROM php:8.2-fpm
Dockerfile-7.4 → FROM php:7.4-fpm
Dockerfile-5.6 → FROM php:5.6-fpm
Danh sách extension có thể khác đôi chút giữa các phiên bản (một số extension không có cho PHP cũ), nhưng pattern là giống nhau.
Chạy Artisan, Composer, v.v.
Vì PHP chạy bên trong container, bạn cần exec vào:
# Vào container PHP 8.4
make bash n=php84
# Bạn đang ở trong container tại /var/www/html
cd blog.md
php artisan migrate
composer install
php artisan test
Hoặc chạy lệnh đơn lẻ không cần vào container:
docker exec php84 bash -c "cd /var/www/html/blog.md && php artisan migrate"
Nhiều Phiên Bản MySQL
Tại Sao Cần Nhiều Phiên Bản?
Tình huống thực tế: project mới dùng MySQL 8.4, nhưng project cũ của khách hàng vẫn trên MySQL 5.7 với feature bị deprecated ở 8.x. Chạy cả hai cùng lúc tránh được vấn đề tương thích.
docker-compose.yml — Database Services
mysql-5.7:
restart: unless-stopped
container_name: "mysql-5.7"
environment:
MYSQL_ROOT_PASSWORD: ${AIO_PASSWORD}
build:
context: ./tools/mysql-5.7
dockerfile: Dockerfile
volumes:
- ./httpdocs:/data:delegated # Truy cập file project
- mysql-5.7-data:/var/lib/mysql:delegated # Lưu trữ bền vững
networks:
- proxynet
ports:
- 3306:3306 # Truy cập từ host tại localhost:3306
mysql-8.0:
restart: unless-stopped
container_name: "mysql-8.0"
environment:
MYSQL_ROOT_PASSWORD: ${AIO_PASSWORD}
build:
context: ./tools/mysql-8.0
dockerfile: Dockerfile
volumes:
- ./httpdocs:/data:delegated
- mysql-8.0-data:/var/lib/mysql:delegated
networks:
- proxynet
ports:
- 3308:3306 # Truy cập từ host tại localhost:3308
mysql-8.4:
restart: unless-stopped
container_name: "mysql-8.4"
environment:
MYSQL_ROOT_PASSWORD: ${AIO_PASSWORD}
build:
context: ./tools/mysql-8.4
dockerfile: Dockerfile
volumes:
- ./httpdocs:/data:delegated
- mysql-8.4-data:/var/lib/mysql:delegated
networks:
- proxynet
ports:
- 3309:3306 # Truy cập từ host tại localhost:3309
Chiến Lược Port Mapping
| Service | Port Container | Port Host | Kết nối từ PHP |
|---|---|---|---|
| mysql-5.7 | 3306 | 3306 | mysql-5.7:3306 |
| mysql-8.0 | 3306 | 3308 | mysql-8.0:3306 |
| mysql-8.4 | 3306 | 3309 | mysql-8.4:3306 |
Từ PHP container (bên trong Docker network): Dùng tên container và port 3306.
Từ máy host (TablePlus, DBeaver): Dùng localhost và port host đã map.
Cấu Hình Laravel .env
# Cho project dùng MySQL 8.4
DB_CONNECTION=mysql
DB_HOST=mysql-8.4 # Tên container, KHÔNG phải localhost!
DB_PORT=3306 # Port nội bộ, không phải port mapped
DB_DATABASE=my_app
DB_USERNAME=root
DB_PASSWORD=your_password
Lỗi thường gặp: Dùng
localhosthoặc127.0.0.1làmDB_HOSTtrong Laravel. Bên trong Docker container,localhostchỉ đến chính container đó, không phải máy host. Luôn dùng tên container.
Lưu Trữ Bền Vững Với Named Volumes
volumes:
mysql-5.7-data:
mysql-8.0-data:
mysql-8.4-data:
Named volumes sống sót qua việc rebuild container. Bạn có thể make reup n=mysql-5.7 suốt ngày mà dữ liệu vẫn an toàn. Để thực sự xóa dữ liệu:
docker volume rm docker-starfish_mysql-5.7-data
Dev Tools
Adminer — Quản Lý Database
adminer:
restart: unless-stopped
image: adminer
container_name: "adminer"
environment:
ADMINER_DEFAULT_SERVER: mysql-5.7
networks:
- proxynet
Truy cập qua http://adminer.dev.local. Proxy tự động route nhờ directive map trong default.conf.
Để kết nối đến các phiên bản MySQL khác nhau từ Adminer:
- Server:
mysql-5.7,mysql-8.0, hoặcmysql-8.4 - Username:
root - Password:
AIO_PASSWORDtừ.env
Mailpit — Test Email
mailpit:
restart: unless-stopped
image: axllent/mailpit
container_name: "mailpit"
networks:
- proxynet
volumes:
- mailpit-data:/data
ports:
- 8025:8025 # Giao diện web
- 1025:1025 # SMTP
environment:
MP_MAX_MESSAGES: 5000
MP_DATABASE: /data/mailpit.db
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
Mailpit bắt toàn bộ email gửi đi. Cấu hình Laravel gửi mail đến Mailpit:
MAIL_MAILER=smtp
MAIL_HOST=mailpit # Tên container
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
Truy cập giao diện web tại http://mailpit.dev.local để xem tất cả email đã bắt được.
pgAdmin4 — Quản Lý PostgreSQL
pgadmin4:
restart: unless-stopped
container_name: pgadmin4
image: dpage/pgadmin4:latest
environment:
- PGADMIN_DEFAULT_EMAIL=${PGADMIN_EMAIL}
- PGADMIN_DEFAULT_PASSWORD=${AIO_PASSWORD}
- PGADMIN_LISTEN_PORT=80
networks:
- proxynet
volumes:
- pgadmin4-data:/var/lib/pgadmin
Truy cập qua http://pgadmin4.dev.local.
File .env
Toàn bộ password và cấu hình tập trung trong .env:
# Dùng bởi docker-compose
AIO_PASSWORD=your_secure_password
AIO_USERNAME=root
PGADMIN_EMAIL=admin@local.dev
LOCAL_COMPOSE_FILE=docker-compose.yml
OS= # Để trống cho Linux/Mac
Không bao giờ commit
.envlên git. Thêm vào.gitignore.
Quy Trình Làm Việc Hàng Ngày
Bắt Đầu Ngày Làm Việc
cd docker-starfish
make up # Khởi động tất cả container
make ls # Kiểm tra mọi thứ đang chạy
NAMES STATUS PORTS
proxy Up 2 minutes 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
php84 Up 2 minutes 9000/tcp
php82 Up 2 minutes 9000/tcp
mysql-8.4 Up 2 minutes 0.0.0.0:3309->3306/tcp
adminer Up 2 minutes 8080/tcp
mailpit Up 2 minutes 0.0.0.0:1025->1025/tcp, 0.0.0.0:8025->8025/tcp
Làm Việc Với Project
make bash n=php84 # Vào container PHP
cd blog.md # Đến thư mục project
composer install # Cài dependency
php artisan migrate # Chạy migration
Sau Khi Thay Đổi Docker Config
make reup n=proxy # Rebuild proxy sau khi sửa vhost
make reup n=php84 # Rebuild PHP sau khi sửa Dockerfile
make reup # Rebuild tất cả
Kiểm Tra Tài Nguyên
make ss # docker stats — CPU, memory, network
Sơ Đồ Kiến Trúc Hoàn Chỉnh
┌─────────────────────────────────────────────────────────────┐
│ Máy Host │
│ │
│ /etc/hosts ./httpdocs/ (volume chung) │
│ 127.0.0.1 blog.dev.local ├── blog.md/ │
│ 127.0.0.1 adminer.dev.local ├── my-project/ │
│ 127.0.0.1 mailpit.dev.local └── legacy-app/ │
│ │
│ Ports: :80, :443, :3306, :3308, :3309, :1025, :8025 │
└──────────────────────────┬──────────────────────────────────┘
│
Docker Network: proxynet
│
┌──────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌───────────────────────────┐ ┌──────────────────┐
│ proxy │ │ PHP-FPM Containers │ │ Databases │
│ :80 │──│ php84:9000 php82:9000 │ │ mysql-5.7:3306 │
│ :443 │ │ php74:9000 php56:9000 │ │ mysql-8.0:3306 │
└────────┘ └───────────────────────────┘ │ mysql-8.4:3306 │
└──────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ Dev Tools │
│ adminer:8080 mailpit:8025/1025 pgadmin4:80 │
└──────────────────────────────────────────────────────────┘
Tổng Kết
| Thành phần | Mục đích | Truy cập |
|---|---|---|
| Proxy (Nginx) | Route toàn bộ traffic | *.dev.local:80/443 |
| PHP 8.4 | App PHP hiện đại | qua fastcgi_pass php84:9000 |
| PHP 7.4 | App cũ | qua fastcgi_pass php74:9000 |
| MySQL 5.7 | Database cũ | mysql-5.7:3306 / localhost:3306 |
| MySQL 8.4 | Database mới | mysql-8.4:3306 / localhost:3309 |
| Adminer | Quản lý DB | adminer.dev.local |
| Mailpit | Test email | mailpit.dev.local |
Setup này đã phục vụ tôi qua hàng chục project — từ WordPress PHP 5.6 legacy đến Laravel 11 hiện đại, tất cả chạy đồng thời trên một máy không xung đột.
Điều hướng Series:
- ← Phần 1: Kiến Trúc
- ← Phần 2: Nginx Reverse Proxy
- Phần 3: PHP, Database & Dev Tools (Bạn đang ở đây)