- Published on
Hướng dẫn Deploy PHP lên VPS: Self-hosting với Docker và Nginx
- Authors

- Name
- Xiro The Dev
Deploy PHP lên VPS là một kỹ năng cần thiết cho các developer muốn tự quản lý infrastructure của mình. So với các shared hosting services, việc self-host trên VPS cho bạn kiểm soát hoàn toàn, tùy chỉnh cao và hiệu năng tốt hơn. Bài viết này sẽ hướng dẫn bạn deploy PHP app lên VPS sử dụng Docker, MySQL và Nginx.
Table of Contents
Tổng quan
Hướng dẫn này sẽ hướng dẫn bạn deploy ứng dụng PHP đơn giản với MySQL database trên Ubuntu server sử dụng Docker và Nginx. Chúng ta sẽ sử dụng ví dụ từ thư mục examples trong repository này.
📦 Xem source code trên GitHubStack công nghệ:
| Component | Technology | Mục đích |
|---|---|---|
| Application | PHP 8.2 | Server-side scripting language |
| Database | MySQL 8.0 | Relational database |
| Containerization | Docker & Docker Compose | Isolate và quản lý containers |
| Web Server | Nginx | Reverse proxy, SSL termination, PHP-FPM handler |
| PHP Runtime | PHP-FPM | FastCGI Process Manager cho PHP |
| SSL | Let's Encrypt | HTTPS encryption |
| OS | Ubuntu Linux | Server operating system |
Tại sao Self-hosting PHP?
Ưu điểm:
- ✅ Kiểm soát hoàn toàn infrastructure
- ✅ Chi phí có thể thấp hơn managed hosting
- ✅ Tùy chỉnh PHP extensions và config
- ✅ Hiểu rõ cách hệ thống hoạt động
- ✅ Không bị giới hạn bởi hosting provider rules
Nhược điểm:
- ❌ Cần quản lý và maintain server
- ❌ Trách nhiệm về security và updates
- ❌ Cần kiến thức về DevOps
- ❌ Không có managed scaling tự động
Prerequisites
Trước khi bắt đầu, bạn cần chuẩn bị:
1. Domain Name
Mua một domain name và chuẩn bị cấu hình DNS:
- Tạo A record trỏ đến IP của VPS
- Thời gian propagate: thường 5-30 phút, có thể đến 48 giờ
2. VPS Server
Yêu cầu tối thiểu cho PHP app:
- RAM: 1GB+ (khuyến nghị 2GB+)
- CPU: 1 core+ (khuyến nghị 2 cores)
- Storage: 20GB+ (khuyến nghị 40GB+)
- OS: Ubuntu 20.04 LTS hoặc mới hơn
Nơi mua VPS:
- DigitalOcean (Droplets)
- AWS EC2
- Azure VMs
- Linode
- Vultr
3. Kiến thức cơ bản
- SSH và command line
- Git basics
- Hiểu cơ bản về Docker
- Quen thuộc với PHP
Kiến trúc Deployment
Luồng request:
User → Domain → DNS → Nginx (Port 80/443)
↓
Docker Network
↓
┌─────────┴─────────┐
↓ ↓
PHP-FPM Container MySQL Container
(Port 9000) (Port 3306)
Các thành phần:
- Nginx: Reverse proxy, xử lý SSL, route requests đến PHP-FPM
- PHP-FPM Container: Chạy PHP application với PHP-FPM
- MySQL Container: Database server
- Docker Network: Kết nối các containers
Setup Server
1. SSH vào Server
ssh root@your_server_ip
# hoặc nếu dùng non-root user
ssh user@your_server_ip
2. Update hệ thống
sudo apt update && sudo apt upgrade -y
3. Cài đặt các packages cơ bản
sudo apt install -y curl wget git build-essential
Cài đặt Docker và Docker Compose
Cài đặt Docker
# Remove old versions
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
# Install latest version
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Start Docker service
sudo systemctl start docker
sudo systemctl enable docker
# Verify installation
docker --version
Thêm user vào docker group (optional)
sudo usermod -aG docker $USER
newgrp docker
Setup Nginx
Phần này hướng dẫn cài đặt và cấu hình Nginx trên Ubuntu 20.04, dựa trên hướng dẫn chính thức của DigitalOcean.
Cài đặt Nginx
# Update package index
sudo apt update
# Install Nginx
sudo apt install nginx
# Verify installation
nginx -v
Điều chỉnh Firewall
Trước khi kiểm tra Nginx, bạn cần điều chỉnh firewall để cho phép traffic:
# Check firewall status
sudo ufw status
# Nếu firewall chưa active, enable nó
sudo ufw enable
# Allow OpenSSH (quan trọng - nếu không bạn có thể bị lock out)
sudo ufw allow 'OpenSSH'
# Allow Nginx traffic
sudo ufw allow 'Nginx Full'
# Verify firewall rules
sudo ufw status
WARNING
Quan trọng: Luôn cho phép OpenSSH trước khi enable firewall, nếu không bạn có thể bị lock out khỏi server!
Kiểm tra Web Server
# Check Nginx status
sudo systemctl status nginx
# Nếu chưa chạy, start Nginx
sudo systemctl start nginx
# Enable Nginx để tự động start khi reboot
sudo systemctl enable nginx
Cấu hình Server Blocks cho PHP
# Tạo file config cho domain của bạn
sudo nano /etc/nginx/sites-available/your-domain.com
Nội dung file config cho PHP với PHP-FPM:
server {
listen 80;
listen [::]:80;
server_name your-domain.com www.your-domain.com;
root /var/www/html;
index index.php index.html index.htm;
# Logging
access_log /var/log/nginx/your-domain.access.log;
error_log /var/log/nginx/your-domain.error.log;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass php-fpm:9000; # PHP-FPM container
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
Cấu hình PHP Application
1. Cấu trúc Project
Từ thư mục examples View on GitHub, project có cấu trúc:
php-simple-app/
├── src/ # PHP source code
│ ├── index.php # Main entry point
│ ├── config.php # Configuration
│ └── db.php # Database connection
├── public/ # Public files (assets)
├── Dockerfile # PHP-FPM container definition
├── docker-compose.yml # Multi-container setup
├── nginx.conf # Nginx configuration
├── deploy.sh # Deployment script
└── .env.example # Environment variables example
2. Dockerfile cho PHP-FPM
FROM php:8.2-fpm-alpine
# Install system dependencies
RUN apk add --no-cache \
git \
curl \
libpng-dev \
libzip-dev \
zip \
unzip \
mysql-client
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mysqli zip gd
# Install Composer
COPY /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www/html
# Copy application files
COPY src/ /var/www/html/
COPY public/ /var/www/html/public/
# Set permissions
RUN chown -R www-data:www-data /var/www/html
RUN chmod -R 755 /var/www/html
# Expose PHP-FPM port
EXPOSE 9000
CMD ["php-fpm"]
3. docker-compose.yml
services:
nginx:
image: nginx:alpine
ports:
- '80:80'
- '443:443'
volumes:
- ./src:/var/www/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- php-fpm
networks:
- app_network
php-fpm:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./src:/var/www/html
- ./public:/var/www/html/public
environment:
- DB_HOST=mysql
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
depends_on:
- mysql
networks:
- app_network
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
ports:
- '3306:3306'
networks:
- app_network
volumes:
mysql_data:
networks:
app_network:
driver: bridge
4. Ví dụ PHP Application (index.php)
<?php
require_once 'config.php';
require_once 'db.php';
$db = new Database();
$conn = $db->getConnection();
// Example query
$stmt = $conn->prepare("SELECT * FROM users LIMIT 10");
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHP Simple App</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #4CAF50;
color: white;
}
.info {
background: #e3f2fd;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 PHP Application trên VPS</h1>
<div class="info">
<strong>Server Info:</strong><br>
PHP Version: <?php echo phpversion(); ?><br>
Server: <?php echo $_SERVER['SERVER_SOFTWARE']; ?><br>
Document Root: <?php echo $_SERVER['DOCUMENT_ROOT']; ?>
</div>
<h2>Database Connection: ✅ Connected</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Created At</th>
</tr>
</thead>
<tbody>
<?php if (empty($users)): ?>
<tr>
<td colspan="4">No users found. Run database migrations first.</td>
</tr>
<?php else: ?>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo htmlspecialchars($user['id']); ?></td>
<td><?php echo htmlspecialchars($user['name']); ?></td>
<td><?php echo htmlspecialchars($user['email']); ?></td>
<td><?php echo htmlspecialchars($user['created_at']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</body>
</html>
Cấu hình MySQL
Database Setup
Sau khi containers chạy, setup database:
# Vào MySQL container
docker exec -it php-simple-app-mysql-1 mysql -u root -p
# Tạo database và user (nếu chưa có)
CREATE DATABASE IF NOT EXISTS myapp_db;
CREATE USER IF NOT EXISTS 'myuser'@'%' IDENTIFIED BY 'mypassword';
GRANT ALL PRIVILEGES ON myapp_db.* TO 'myuser'@'%';
FLUSH PRIVILEGES;
EXIT;
Tạo Tables
# Vào PHP container
docker exec -it php-simple-app-php-fpm-1 sh
# Hoặc import SQL file
docker exec -i php-simple-app-mysql-1 mysql -u myuser -pmypassword myapp_db < schema.sql
Ví dụ schema.sql:
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (name, email) VALUES
('John Doe', 'john@example.com'),
('Jane Smith', 'jane@example.com'),
('Bob Johnson', 'bob@example.com');
Deploy Script
Deploy script tự động hóa toàn bộ quá trình deployment:
Các bước script thực hiện:
| Bước | Công việc | Chi tiết |
|---|---|---|
| 1. Setup Packages | Cài đặt dependencies | curl, git, Docker, Docker Compose, Nginx |
| 2. Clone Repository | Download source code | Clone từ Git hoặc copy local files |
| 3. Generate SSL | Tạo SSL certificate | Sử dụng Certbot (Let's Encrypt) |
| 4. Build Application | Build PHP-FPM container | Docker build với Dockerfile |
| 5. Configure Nginx | Setup reverse proxy | Cấu hình HTTPS, PHP-FPM integration |
| 6. Setup Database | Initialize MySQL | Tạo database và tables |
| 7. Start Services | Khởi động containers | docker-compose up -d |
| 8. Setup Cron | Automated tasks | SSL renewal, backups |
Sử dụng Deploy Script
# Download script
curl -o ~/deploy-php.sh https://raw.githubusercontent.com/xirothedev/blog-tech/main/deploy-php.sh
# Chỉnh sửa các biến
nano ~/deploy-php.sh
# Thay đổi: EMAIL, DOMAIN_NAME, DB credentials
# Chạy script
chmod +x ~/deploy-php.sh
./deploy-php.sh
Ví dụ deploy.sh
#!/bin/bash
set -e
# Configuration
EMAIL="your-email@example.com"
DOMAIN="your-domain.com"
PROJECT_DIR="/opt/php-app"
echo "Starting PHP deployment..."
# Update system
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Install Nginx
sudo apt install -y nginx
# Copy project files
if [ -d "$PROJECT_DIR" ]; then
cd $PROJECT_DIR && git pull
else
git clone https://github.com/xirothedev/blog-tech/php-app.git $PROJECT_DIR
cd $PROJECT_DIR
fi
# Generate SSL certificate
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d $DOMAIN --non-interactive --agree-tos -m $EMAIL
# Build and start containers
cd $PROJECT_DIR
sudo docker-compose build
sudo docker-compose up -d
echo "Deployment completed!"
SSL Certificate với Let's Encrypt
Cài đặt Certbot
sudo apt install -y certbot python3-certbot-nginx
Generate SSL Certificate
# Automatic configuration với Nginx
sudo certbot --nginx -d your-domain.com
# Hoặc chỉ generate certificate
sudo certbot certonly --nginx -d your-domain.com
Auto-renewal
Certbot tự động setup cron job để renew certificate:
# Test renewal
sudo certbot renew --dry-run
# Check renewal status
sudo systemctl status certbot.timer
Rate Limiting và Security
Nginx Rate Limiting
# /etc/nginx/nginx.conf
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location / {
limit_req zone=api_limit burst=20 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
}
}
Security Headers
server {
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Hide PHP version
fastcgi_hide_header X-Powered-By;
}
PHP Security Settings
Trong php.ini:
; Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
; Hide PHP version
expose_php = Off
; Error reporting (production)
display_errors = Off
log_errors = On
Update và Maintenance
Maintenance Commands
| Mục đích | Command | Mô tả |
|---|---|---|
| Check containers | docker-compose ps | Xem trạng thái containers |
| View PHP logs | docker-compose logs php-fpm | Xem logs của PHP-FPM |
| View Nginx logs | docker-compose logs nginx | Xem logs của Nginx |
| View MySQL logs | docker-compose logs mysql | Xem logs của MySQL |
| Restart services | docker-compose restart | Khởi động lại tất cả services |
| Stop services | docker-compose down | Dừng và xóa containers |
| Start services | docker-compose up -d | Khởi động containers trong background |
| Enter PHP container | docker exec -it php-fpm-1 sh | Vào trong PHP container |
| Enter MySQL container | docker exec -it mysql-1 mysql -u root -p | Vào MySQL CLI |
Backup Strategy
| Loại Backup | Tần suất | Cách thực hiện |
|---|---|---|
| Database Backup | Daily | mysqldump hoặc Docker volume backup |
| Application Code | Per deployment | Git repository |
| Environment Variables | On changes | Secure storage |
| Docker Volumes | Weekly | Volume backup scripts |
Backup Database
# Backup MySQL
docker exec php-simple-app-mysql-1 mysqldump -u myuser -pmypassword myapp_db > backup_$(date +%Y%m%d).sql
# Restore from backup
docker exec -i php-simple-app-mysql-1 mysql -u myuser -pmypassword myapp_db < backup_20250101.sql
Troubleshooting
Các vấn đề thường gặp
| Vấn đề | Nguyên nhân có thể | Giải pháp |
|---|---|---|
| 502 Bad Gateway | PHP-FPM không chạy | Kiểm tra docker-compose ps và logs |
| Database connection error | Connection string sai | Kiểm tra DB credentials trong .env |
| Nginx 404 | File paths không đúng | Kiểm tra root và index settings |
| PHP errors không hiện | display_errors = Off | Kiểm tra php.ini hoặc logs |
| SSL certificate fail | DNS chưa propagate | Đợi DNS update |
Debug Commands
# Check Docker containers
docker ps -a
# Check Docker logs
docker-compose logs --tail=100 php-fpm
docker-compose logs --tail=100 nginx
# Check Nginx config
sudo nginx -t
# Check PHP-FPM status
docker exec php-fpm-1 php-fpm -v
# Test database connection
docker exec mysql-1 mysql -u myuser -pmypassword -e "SHOW DATABASES;"
Best Practices
1. Environment Variables
| Biến | Mô tả | Bảo mật |
|---|---|---|
DB_HOST | MySQL host | ✅ Không commit vào Git |
DB_NAME | Database name | ⚠️ Có thể commit nếu không sensitive |
DB_USER | Database user | ✅ Không commit |
DB_PASSWORD | Database password | ✅ Không commit |
APP_ENV | Environment (prod/dev) | ✅ Set trong Docker |
2. PHP Best Practices
| Practice | Benefit | Implementation |
|---|---|---|
| Use prepared statements | Prevent SQL injection | PDO or mysqli |
| Error handling | Better debugging | try-catch blocks |
| Input validation | Security | Filter input, validate |
| Cache opcache | Performance | Enable OPcache |
| Composer autoload | Better code organization | Use PSR-4 autoloading |
3. Docker Best Practices
| Practice | Lý do | Implementation |
|---|---|---|
| Multi-stage builds | Giảm image size | Separate build và runtime |
| Non-root user | Security | Run với www-data user |
| .dockerignore | Faster builds | Exclude vendor, .git |
| Health checks | Auto-recovery | Healthcheck trong docker-compose |
| Resource limits | Prevent exhaustion | Set CPU/memory limits |
4. Security Checklist
- ✅ Keep system updated
- ✅ Use strong passwords
- ✅ Enable firewall (UFW)
- ✅ SSH key authentication
- ✅ Regular security audits
- ✅ Monitor logs
- ✅ Use HTTPS only
- ✅ Security headers trong Nginx
- ✅ Rate limiting
- ✅ Database access restrictions
- ✅ Hide PHP version
- ✅ Disable dangerous PHP functions
Kết luận
Deploy PHP lên VPS là một quá trình có thể phức tạp nhưng mang lại nhiều lợi ích: kiểm soát hoàn toàn, chi phí linh hoạt và hiệu năng tốt. Với Docker và Nginx, bạn có thể setup một production-ready deployment.
Điểm mấu chốt
- ✅ Docker simplifies deployment - Containerization giúp dễ quản lý
- ✅ Nginx + PHP-FPM - Hiệu năng tốt cho PHP applications
- ✅ MySQL for data - Reliable database solution
- ✅ SSL/HTTPS - Bảo mật cho production
- ✅ Automation is key - Deploy scripts giúp tiết kiệm thời gian
Tài nguyên tham khảo
- 📦 PHP Simple App Example - Source code mẫu View on GitHub
- 📚 PHP Documentation
- 🐳 Docker Documentation
- 🌐 Nginx Documentation
- 📘 MySQL Documentation
Bước tiếp theo
- Clone example project: Thử nghiệm với php-simple-app
- Customize: Tùy chỉnh theo nhu cầu của bạn
- Deploy: Chạy deploy script trên VPS
- Monitor: Setup monitoring và alerting
- Optimize: Tối ưu performance
TIP
Bắt đầu với example: Sử dụng php-simple-app như một starting point để hiểu cách setup và customize cho project của bạn!