Đã bao giờ bạn toát mồ hôi hột lúc 2 giờ sáng chỉ vì lỡ tay gõ lệnh update phiên bản PHP trên server bare-metal, làm sập toàn bộ hàng chục website khách hàng đang chạy chung? Hay chán nản tột độ với cảnh phải lặn lội dọn dẹp hàng tá file rác, gỡ rối database conflict tung mù sau khi uninstall một cái script cài đặt cũ rích? Tệ hơn nữa là cảnh ổ cứng VPS báo đỏ chót No space left on device do file log phình to vô tội vạ khiến máy chủ ngừng hoạt động.
Thực tế, việc maintain các hạ tầng server bằng phương pháp cài cắm trực tiếp (LAMP/LEMP stack truyền thống) hay thông qua các tài liệu hướng dẫn thiết lập WordPress trên VPS bằng các phần mềm và tập lệnh đang trở thành một di sản cồng kềnh, là cơn ác mộng thực sự đối với mọi developer và web agency khi cần scale dự án. Thay vì tự làm khổ mình với mớ bòng bong dependency (sự phụ thuộc phần mềm), tại sao bạn không đóng gói mọi thứ lại một cách thông minh, độc lập và an toàn hơn?
Bài viết này sẽ hướng dẫn bạn chi tiết cách cài WordPress Docker Compose, tiêu chuẩn triển khai hạ tầng website mượt mà và an toàn nhất tính đến thời điểm hiện tại. Sự kết hợp giữa kiến trúc Container hiện đại và Nginx Proxy Manager (NPM) không chỉ giúp bạn tránh những bẫy thực chiến chí mạng mà còn biến hệ thống của bạn thành một cỗ máy chịu tải tuyệt vời. Bạn đã sẵn sàng để dọn dẹp lại mớ hỗn độn trên VPS của mình và bước sang một kỷ nguyên quản trị nhàn nhã hơn chưa?
Quên kịch bản cài tay đi: Kiến trúc Container giải quyết được gì?
Trước đây, khi setup một web server, bạn sẽ phải cài tuần tự hệ điều hành Linux, Web Server (Nginx hoặc Apache), Database (MySQL hoặc MariaDB), và PHP. Cách làm tất cả chung một giỏ này tồn tại một điểm yếu chí mạng: Mọi website trên server đều phải dùng chung một môi trường hệ thống. Nếu Website A cần PHP 7.4 để chạy một plugin cũ, nhưng Website B lại yêu cầu PHP 8.3 mới nhất, bạn sẽ lập tức rơi vào dependency hell (địa ngục xung đột phần mềm).
Khi dịch chuyển sang sử dụng Docker, bạn đang áp dụng triết lý Infrastructure as Code (Quản lý hạ tầng bằng mã code). Mỗi một website giờ đây là một cụm (stack) hoàn toàn độc lập, sở hữu Web Server, PHP và Database riêng biệt.
- Tính cô lập (Isolation) tuyệt đối: Website A bị dính mã độc, crash do thiếu RAM, hoặc bị quá tải request? Không sao cả! Website B nằm ở container bên cạnh vẫn hoạt động trơn tru mà không hề hay biết, bởi chúng bị giới hạn tài nguyên độc lập.
- Zero Downtime Migration (Di chuyển không gián đoạn): Cần chuyển nhà sang một VPS mới mạnh mẽ hơn? Bạn không cần phải setup lại server từ đầu. Chỉ cần rsync thư mục chứa file
docker-compose.ymlvà thư mụcvolumes(chứa data), sang máy mới gõ lệnh khởi động là hệ thống khôi phục 100% trong chưa tới 5 phút. - Bảo mật tuyệt đối tầng Network: Với mô hình Docker hóa, Database (như MariaDB) sẽ không bao giờ bị expose port
3306ra ngoài Internet. Nó được bọc kín đáo trong một mạng nội bộ (internal network), chỉ có container WordPress nằm trong cùng stack mới được phép gọi đến nó. Hacker rà soát IP từ bên ngoài sẽ không bao giờ thấy database của bạn tồn tại.
Và mảnh ghép hoàn hảo đi kèm với Docker chính là Nginx Proxy Manager (NPM). Thay vì phải ssh vào server ngồi gõ từng dòng lệnh cấu hình block Nginx chay cực nhọc để trỏ domain, proxy pass, và cấu hình certbot để lấy chứng chỉ Let’s Encrypt, NPM cung cấp cho bạn một giao diện Web UI trực quan. Bạn có thể route traffic cho hàng trăm domain, bật HTTP/2, kích hoạt cache tĩnh chỉ bằng vài cú click chuột.
Chuẩn bị VPS và cài đặt Docker Engine (2026)
Để chạy mượt mà hệ sinh thái container này và đảm bảo khả năng chịu tải tốt, bạn cần chuẩn bị một hạ tầng server đáp ứng đủ tiêu chuẩn hiện đại.
Cấu hình VPS tối thiểu cho Production
Nhiều người lầm tưởng Docker nặng, nhưng thực tế nó tiêu tốn cực ít overhead (tài nguyên lãng phí) so với máy ảo (VM). Tuy nhiên, vì chúng ta sẽ chạy MariaDB, PHP-FPM/Apache và Redis Cache trong bộ nhớ, bạn cần chú ý:
- CPU: Ít nhất 2 vCPU. Các luồng xử lý độc lập giúp PHP-FPM giải quyết concurrent requests tốt hơn.
- RAM: Tối thiểu 2GB. Tuy nhiên, khuyến nghị mức 4GB trở lên nếu bạn định chạy từ 3 website cùng lúc hoặc bật Redis Object Cache. Dưới 2GB, trình quản lý nhân Linux (OOM Killer) rất dễ can thiệp và ngắt ngang tiến trình Database khi có traffic đột biến.
- Ổ cứng: SSD NVMe 50GB trở lên. Tốc độ I/O của NVMe là bắt buộc để database hoạt động trơn tru.
- OS: Ubuntu 24.04 LTS hoặc Debian 12 (ưu tiên các bản LTS để hệ thống ổn định lâu dài).
- Trước khi bắt tay vào chạy lệnh Docker, bạn nên tham khảo qua danh sách checklist tối ưu VPS Ubuntu 24.04 để đảm bảo máy chủ được tinh chỉnh các tham số hệ thống tốt nhất.
Lệnh cài đặt Docker & Docker Compose chuẩn xác nhất
Truy cập SSH vào VPS của bạn bằng quyền root (hoặc user có quyền sudo). Hãy dọn dẹp các bản cũ và cài đặt môi trường mới nhất bằng loạt lệnh thực chiến sau:
Cập nhật cache của package manager và nâng cấp hệ thống:
sudo apt update && sudo apt upgrade -y
Gỡ bỏ sạch sẽ các phiên bản Docker cũ, lỗi thời (nếu có):
sudo apt remove docker docker-engine docker.io containerd runc -y
Cài đặt Docker Engine chính thức qua script tự động của Docker:
curl -fsSL https://get.docker.com | sudo bash
Cài đặt plugin Docker Compose (phiên bản v2, không dùng bản v1 có dấu gạch ngang):
sudo apt install docker-compose-plugin -y
Thêm user hiện tại vào group docker để không cần gõ lệnh quyền quản trị mỗi lần chạy lệnh:
sudo usermod -aG docker $USER
newgrp docker
(Mẹo: Việc gán user vào group docker giúp bạn thao tác nhanh hơn rất nhiều, tránh lỗi permission denied khi quên gõ sudo).
Triển khai Nginx Proxy Manager (NPM)
NPM sẽ đóng vai trò là chiếc cổng kết nối trung tâm (Reverse Proxy) duy nhất mở ra Internet. Chúng ta sẽ tạo một thư mục gốc ở thư mục home chuyên để quản lý các dịch vụ.
mkdir -p ~/docker/npm
cd ~/docker/npm
Tại đây, bạn tạo file docker-compose.yml cho NPM.
Cảnh báo bẫy thực chiến: Rất nhiều developer mới làm quen với Docker thường bỏ qua việc quản lý Log. Mặc định, Docker lưu log dưới dạng JSON vô hạn. Khi website bị DDoS hoặc dính lỗi liên tục, file log này có thể phình to lên hàng chục GB, nuốt trọn ổ cứng VPS. Do đó, chúng ta bắt buộc phải cấu hình Log Rotation (xoay vòng log) ngay tại đây.
version: '3.8'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- '80:80' # Lắng nghe traffic HTTP
- '443:443' # Lắng nghe traffic HTTPS
- '81:81' # Cổng truy cập Web UI Admin của NPM
environment:
TZ: "Asia/Ho_Chi_Minh"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- proxy_network
logging: # BẮT BUỘC: Cấu hình giới hạn dung lượng log
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
proxy_network:
name: proxy_network
driver: bridge
Sau này, khi hệ thống phình to, bạn có thể tham khảo cách triển khai giải pháp quản lý log VPS bằng Grafana Loki để theo dõi log tập trung theo thời gian thực.
Khởi chạy NPM bằng lệnh:
docker compose up -d
Sau vài chục giây để container pull image và khởi động, bạn hãy mở trình duyệt và truy cập http://<IP_CỦA_VPS>:81.
Sử dụng tài khoản mặc định (admin@example.com / changeme) để đăng nhập và ngay lập tức đổi sang một mật khẩu đủ mạnh. Web UI của NPM đã sẵn sàng!
Đừng quên cấu hình firewall chặn port 81 từ public IP sau khi đã setup xong để tăng cường bảo mật. Nếu chưa quen thao tác, hãy xem ngay các hướng dẫn bảo mật VPS Linux với UFW và Fail2Ban.
Thực chiến cài WordPress Docker Compose (có MariaDB & Redis)
Bây giờ là lúc chứng minh sức mạnh thực sự của Container. Chúng tối sẽ setup một stack WordPress hoàn chỉnh, an toàn cho domain giả định là myagency.com.
mkdir -p ~/docker/sites/myagency
cd ~/docker/sites/myagency
Bẫy ký tự đặc biệt trong file .env
Tuyệt đối không để mật khẩu dạng plain-text thẳng trong file docker-compose.yml. Hãy tạo file .env (file ẩn) để quản lý cấu hình.
Kinh nghiệm xương máu: Trong cú pháp của Docker Compose, dấu $ được dùng để nội suy biến (variable interpolation). Nếu bạn đặt mật khẩu database là RootPassVeryStrong2026$$, Docker sẽ dịch nhầm cặp $$ thành một dấu $. Kết quả là container WordPress sẽ không thể kết nối được với Database do sai mật khẩu. Để an toàn tuyệt đối, hãy bọc mật khẩu trong dấu nháy đơn '...'.
# File: .env
DB_NAME=wp_agency_db
DB_USER=wp_agency_user
DB_PASS='SuperStrongPass2026!@#'
DB_ROOT_PASS='RootPassVeryStrong2026$$'
WP_TABLE_PREFIX=agc_
Xử lý triệt để giới hạn dung lượng tải lên (uploads.ini)
Image chính thức của WordPress giới hạn file upload mặc định chỉ vỏn vẹn 2MB. Nếu bạn cố upload một cái theme nặng hoặc video, hệ thống báo lỗi ngay. Thay vì vào trong container sửa thủ công, hãy mount một file cấu hình ra ngoài host:
nano uploads.ini
Nhập cấu hình chuẩn sau:
file_uploads = On
memory_limit = 512M
upload_max_filesize = 128M
post_max_size = 128M
max_execution_time = 300
max_input_time = 300
Viết file docker-compose.yml (fix lỗi Race Condition)
Lại một cái bẫy kinh điển khác: Ở lần chạy docker compose up -d đầu tiên, MariaDB cần từ 15-30 giây để khởi tạo cấu trúc bảng dữ liệu (InnoDB). Nếu container WordPress khởi động nhanh hơn, nó sẽ lao vào kết nối với DB chưa sẵn sàng và sinh ra lỗi Error establishing a database connection.
Để giải quyết, chúng ta sử dụng healthcheck trên MariaDB và khai báo condition: service_healthy tại WordPress để ép nó phải ngoan ngoãn chờ đợi.
# File: docker-compose.yml
version: '3.8'
services:
wordpress:
image: wordpress:6.7-php8.3-apache
container_name: agency_wordpress
restart: unless-stopped
depends_on:
db:
condition: service_healthy # Chờ DB thực sự sẵn sàng
redis:
condition: service_started
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: ${DB_NAME}
WORDPRESS_DB_USER: ${DB_USER}
WORDPRESS_DB_PASSWORD: ${DB_PASS}
WORDPRESS_TABLE_PREFIX: ${WP_TABLE_PREFIX}
volumes:
- ./wp_data:/var/www/html
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
networks:
- proxy_network
- internal_wp
logging:
driver: "json-file"
options: { max-size: "10m", max-file: "3" }
db:
image: mariadb:11.4
container_name: agency_db
restart: unless-stopped
environment:
MARIADB_DATABASE: ${DB_NAME}
MARIADB_USER: ${DB_USER}
MARIADB_PASSWORD: ${DB_PASS}
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASS}
volumes:
- ./db_data:/var/lib/mysql
networks:
- internal_wp
healthcheck: # Khám sức khỏe cho Database
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
logging:
driver: "json-file"
options: { max-size: "10m", max-file: "3" }
redis:
image: redis:7-alpine
container_name: agency_redis
restart: unless-stopped
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
networks:
- internal_wp
logging:
driver: "json-file"
options: { max-size: "10m", max-file: "3" }
networks:
proxy_network:
external: true # Bắt buộc khớp với network của NPM
internal_wp:
driver: bridge
internal: true # Network cô lập, chặn đứng rà soát hệ thống từ bên ngoài
Mẹo nâng cao: Nếu website của bạn có lượng truy cập lớn, hãy kết hợp việc chia resource Docker với các kỹ thuật tối ưu database MySQL/MariaDB trên VPS Linux để xử lý hàng vạn query/giây mà không lo tràn RAM.
Xử lý lỗi Permission Denied (điểm nghẽn chí mạng)
Hãy khởi chạy cụm website:
docker compose up -d
Khi bạn dùng tính năng bind mount (./wp_data:/var/www/html) từ host Linux vào trong container, thư mục trên host thường được mặc định gán quyền cho user root. Tuy nhiên, tiến trình WordPress bên trong container lại đang chạy dưới quyền user www-data. Nếu bạn bỏ qua bước này, WordPress sẽ liên tục báo lỗi Permission Denied hoặc đòi thông tin FTP khi bạn cài plugin/theme.
Khắc phục triệt để bằng lệnh phân quyền lại thư mục ngay trên host (33 là UID mặc định của user www-data trên các base image Debian/Apache):
sudo chown -R 33:33 ./wp_data
Trỏ Domain và cấp phát SSL Let’s Encrypt qua NPM
Hãy chắc chắn bạn đã cấu hình bản ghi A của tên miền trỏ về đúng IP của VPS.
- Truy cập vào Web UI của NPM (Port 81).
- Chuyển sang tab Proxy Hosts -> Nhấn Add Proxy Host.
- Tab Details: Nhập tên miền (VD:
myagency.com), Scheme chọnhttp. - Forward Hostname / IP (bẫy 502 Bad Gateway): Bắt buộc nhập tên của container web, trong trường hợp này là
agency_wordpress. Tuyệt đối không nhập localhost hay 127.0.0.1. Tại sao? Vì bản thân NPM đang nằm trong một container của riêng nó. Nếu bạn trỏ về localhost, NPM sẽ tự tìm web server bên trong chính container của nó và dĩ nhiên nó sẽ trả về lỗi 502.- Để hiểu rõ hơn cơ chế và cách bắt bệnh lỗi này, bạn có thể tham khảo bài viết hướng dẫn sửa lỗi 502 Nginx trên VPS chuyên sâu.
- Forward Port: Nhập
80(Do image WordPress bản Apache lắng nghe port 80). Bật Websockets Support và Block Common Exploits. - Tab SSL: Chọn Request a new SSL Certificate, bật Force SSL (bắt buộc HTTPS) và HTTP/2 Support (chuẩn giao thức mới giúp load tài nguyên cực nhanh). Điền Email và nhấn Save.
Đợi vài giây để NPM gọi API của Let’s Encrypt, chứng chỉ sẽ được cấp phát và website của bạn chính thức online với ổ khóa xanh an toàn.
Tối ưu hiệu suất & tự động hóa bảo trì
Hệ thống đã chạy, nhưng để nó trở thành một cỗ máy chịu tải chuyên nghiệp, bạn phải áp dụng hai kỹ thuật cuối cùng này.
Kích hoạt Redis Object Cache bằng WP-CLI (chuẩn bảo mật)
Khi có 100 người dùng truy cập cùng lúc, thay vì ép MariaDB phải thực thi hàng ngàn query liên tục gây nghẽn cổ chai, Redis sẽ cache lại dữ liệu Database trên RAM. Thời gian phản hồi (TTFB) sẽ giảm từ 1 giây xuống chỉ còn vài chục mili-giây.
Thay vì dùng giao diện mất thời gian, chúng ta sẽ gọi thẳng công cụ dòng lệnh WP-CLI. Theo Best Practice về bảo mật của Docker, bạn KHÔNG NÊN chạy lệnh dưới quyền root (cờ --allow-root) mà hãy ép Docker thực thi lệnh dưới quyền user www-data (UID 33):
Khai báo thông vị máy chủ Redis cho hệ thống:
docker exec -u 33 agency_wordpress wp config set WP_REDIS_HOST redis
docker exec -u 33 agency_wordpress wp config set WP_REDIS_PORT 6379
Tải, cài đặt và kích hoạt plugin Redis Object Cache:
docker exec -u 33 agency_wordpress wp plugin install redis-cache --activate
Kích hoạt tính năng Object Cache:
docker exec -u 33 agency_wordpress wp redis enable
Tự động hóa backup cực gọn nhẹ tại Host
Bởi vì toàn bộ mã nguồn của bạn đã được map thẳng ra ngoài thư mục của máy host (chính là ./wp_data), bạn không cần phải tạo các luồng container tạm thời lằng nhằng để nén dữ liệu. Tận dụng sức mạnh của lệnh tar trên Linux sẽ đem lại tốc độ tối đa.
Tạo một bash script tại ~/docker/scripts/backup_wp.sh:
#!/bin/bash
SITE="agency"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/$SITE"
mkdir -p "$BACKUP_DIR"
# Lấy cấu hình password động từ file .env
source ~/docker/sites/myagency/.env
# Dump Database từ container ra ngoài và nén bằng gzip
docker exec ${SITE}_db mysqldump -u root -p"${DB_ROOT_PASS}" ${DB_NAME} | gzip > "$BACKUP_DIR/db_$DATE.sql.gz"
# Nén trực tiếp thư mục mã nguồn ./wp_data đang nằm trên máy host
tar -czf "$BACKUP_DIR/files_$DATE.tar.gz" ~/docker/sites/myagency/wp_data
# Dọn dẹp file backup cũ hơn 14 ngày để chống tràn ổ cứng
find "$BACKUP_DIR" -type f -mtime +14 -delete
Đừng quên cấp quyền thực thi cho file bằng lệnh chmod +x ~/docker/scripts/backup_wp.sh. Cuối cùng, gõ crontab -e và thêm dòng dưới đây để hệ thống tự backup vào lúc 3h sáng mỗi ngày:
0 3 * * * /bin/bash /root/docker/scripts/backup_wp.sh >> /var/log/wp_backup.log 2>&1
Câu hỏi thường gặp (FAQ)
1. Tôi có thể chạy bao nhiêu website WordPress trên một VPS bằng cách này?
Phụ thuộc vào RAM của VPS. Một VPS 2GB RAM hoạt động ổn định 1-2 website (có Redis). Nếu nâng lên 4GB RAM, bạn có thể vận hành mượt mà 3-5 website độc lập. Chỉ cần copy thư mục cấu hình và đổi port/domain.
2. Tại sao tôi cài xong nhưng truy cập domain lại báo lỗi 502 Bad Gateway?
Do cấu hình sai trong Nginx Proxy Manager. Ở ô Forward Hostname, bạn phải điền tên của container WordPress (VD: agency_wordpress). Tuyệt đối không điền localhost hay 127.0.0.1.
3. Tại sao WordPress của tôi không cho upload ảnh hoặc đòi tài khoản FTP khi cài Plugin?
Do lỗi phân quyền volume (Permission Denied). Thoát ra ngoài terminal của VPS và chạy ngay lệnh sudo chown -R 33:33 ./wp_data để cấp lại quyền ghi cho user www-data của WordPress.
4. Dùng Docker thì backup website có phức tạp không?
Dễ và nhanh hơn cài tay rất nhiều. Bạn chỉ cần chạy lệnh dump database ra thành file .sql và dùng lệnh tar để nén luôn thư mục wp_data nằm trên ổ cứng VPS là xong.
5. Tôi có cần mua chứng chỉ SSL bên ngoài để cài vào hệ thống này không?
Hoàn toàn KHÔNG. Nginx Proxy Manager đã tích hợp sẵn API của Let’s Encrypt. Hệ thống sẽ tự động cấp phát và tự động gia hạn SSL cho mọi tên miền của bạn hoàn toàn miễn phí.
6. MariaDB và Redis của tôi có nguy cơ bị hacker rà quét port không?
Không thể. Cả Database và Redis đều được cấu hình chạy trong mạng internal: true. Chúng bị cô lập hoàn toàn khỏi Internet, không mở bất kỳ port nào ra ngoài. Chỉ có WordPress mới nhìn thấy chúng.
Kết luận
Trong kỷ nguyên hạ tầng mạng dịch chuyển liên tục, việc rũ bỏ thói quen quản trị server thủ công để tiến tới tư duy Containerization là bước đi mang tính sống còn. Xuyên suốt bài viết, thông qua việc cài WordPress Docker Compose chuyên nghiệp, bạn không chỉ triển khai thành công một cụm website hiệu suất cao mà còn xử lý triệt để hàng loạt bẫy thực chiến – từ lỗi phân quyền permission, rác log phình to, sự cố Race Condition khó chịu của DB, cho đến lỗi 502 kinh điển khi trỏ sai host.
Sự linh hoạt của kiến trúc này là vũ khí tuyệt vời: Ngày mai nếu bạn chốt deal được thêm 5 khách hàng mới, bạn chỉ cần bỏ ra đúng 2 phút. Copy thư mục myagency, đổi các biến số trong file .env, sửa lại tên container và gõ docker compose up -d. Tất cả vận hành hoàn hảo, an toàn và kiểm soát 100% rủi ro đứt gãy.
