❤️ AZDIGI chính thức cập nhật hệ thống blog mới hoàn chỉnh. Tuy nhiên có thể một số bài viết bị sai lệch hình ảnh, hãy ấn nút Báo cáo bài viết ở cuối bài để AZDIGI cập nhật trong thời gian nhanh nhất. Chân thành cám ơn.

Bài 6, bạn đã làm quen với Docker Compose: công cụ giúp gói toàn bộ hệ thống multi-container vào một file YAML duy nhất. Bạn đã thực hành chạy Nginx + PHP-FPM, học cách viết docker-compose.yml, dùng các lệnh docker compose up/down/ps/logs.

WordPress MySQL phpMyAdmin Docker
Minh họa: WordPress Stack với Docker Compose

Nhưng đó mới chỉ là bài tập. Trong bài này, mình sẽ cùng bạn deploy một ứng dụng thực tế: WordPress + MySQL + phpMyAdmin: 3 services, đầy đủ database, web app, và công cụ quản lý DB. Đây là project gần với production nhất trong serie, và bạn sẽ thấy Docker Compose thực sự mạnh mẽ khi quản lý một stack hoàn chỉnh.

Tổng quan stack: WordPress + MySQL + phpMyAdmin

Trước khi bắt tay vào viết file Compose, hãy hiểu rõ 3 thành phần trong stack:

WordPress

WordPress là CMS (Content Management System) phổ biến nhất thế giới, hơn 40% website trên internet chạy WordPress. Nó cần một database MySQL/MariaDB để lưu trữ nội dung, và một web server (Apache hoặc Nginx) để phục vụ trang web. Image wordpress:latest trên Docker Hub đã đóng gói sẵn Apache + PHP + WordPress core, bạn chỉ cần trỏ nó đến database là chạy.

MySQL

MySQL là hệ quản trị cơ sở dữ liệu quan hệ (RDBMS), nơi WordPress lưu tất cả: bài viết, trang, cài đặt, user, plugin settings. Image mysql:8.0 cho bạn một MySQL server sẵn sàng chạy, chỉ cần cấu hình password và tên database qua environment variables.

phpMyAdmin

phpMyAdmin là công cụ quản lý MySQL qua giao diện web. Thay vì phải exec vào container rồi gõ lệnh SQL, bạn mở trình duyệt, đăng nhập, và thao tác trực quan: xem bảng, chạy query, export/import database. Rất tiện cho việc debug và backup.

Sơ đồ kết nối của stack:

┌─────────────────────────────────────────────────┐
│                  Docker Network                  │
│                  (wp-network)                    │
│                                                  │
│  ┌──────────┐  ┌──────────────┐  ┌────────────┐ │
│  │  MySQL   │←─│  WordPress   │  │ phpMyAdmin │ │
│  │  :3306   │  │  :8080→80    │  │ :8081→80   │ │
│  │          │←─│              │  │            │ │
│  └──────────┘  └──────────────┘  └────────────┘ │
│       ↑                                ↑        │
│       └────────────────────────────────┘        │
│            phpMyAdmin kết nối MySQL              │
└─────────────────────────────────────────────────┘

Cả 3 services đều nằm trong cùng một Docker network, nên chúng có thể giao tiếp với nhau qua tên service (không cần IP). WordPress và phpMyAdmin đều kết nối đến MySQL qua hostname mysql (chính là tên service trong Compose).

Tạo cấu trúc project

Đầu tiên, tạo thư mục project:

mkdir -p ~/wordpress-docker
cd ~/wordpress-docker

Cấu trúc project khi hoàn thành sẽ như sau:

wordpress-docker/
├── docker-compose.yml    # File cấu hình chính
├── .env                  # Biến môi trường (secrets)
└── backups/              # Thư mục chứa backup (tạo sau)

Đơn giản vậy thôi. Không cần Dockerfile, không cần build, tất cả đều dùng image có sẵn trên Docker Hub.

Tạo file .env cho biến môi trường

Trước khi viết docker-compose.yml, mình sẽ tạo file .env để tách riêng các giá trị nhạy cảm (password, tên database) ra khỏi file Compose. Lý do:

  • Bảo mật: Bạn có thể commit docker-compose.yml lên Git mà không lộ password
  • Linh hoạt: Muốn đổi password? Chỉ sửa file .env, không cần sửa compose file
  • Tái sử dụng: Cùng một compose file có thể chạy trên nhiều môi trường (dev, staging, production) với file .env khác nhau

Tạo file .env:

cat > .env << 'EOF'
# MySQL Configuration
MYSQL_ROOT_PASSWORD=SuperSecretRoot@2024
MYSQL_DATABASE=wordpress
MYSQL_USER=wpuser
MYSQL_PASSWORD=WpPass@Secure2024

# WordPress Configuration WORDPRESS_TABLE_PREFIX=wp_

# phpMyAdmin Configuration PMA_PORT=3306 EOF

⚠️ Quan trọng: Đây là password mẫu. Trong thực tế, bạn phải đổi thành password mạnh của riêng mình. Không bao giờ dùng password mặc định trên server production!

Nếu project có dùng Git, thêm .env vào .gitignore ngay:

echo ".env" >> .gitignore

Viết file docker-compose.yml

Đây là phần chính của bài. Mình sẽ viết file docker-compose.yml hoàn chỉnh, sau đó giải thích từng phần:

cat > docker-compose.yml << 'EOF'
services:
  mysql:
    image: mysql:8.0
    container_name: wp-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - wp-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

wordpress: image: wordpress:latest container_name: wp-app restart: unless-stopped depends_on: mysql: condition: service_healthy ports: - "8080:80" environment: WORDPRESS_DB_HOST: mysql:3306 WORDPRESS_DB_USER: ${MYSQL_USER} WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD} WORDPRESS_DB_NAME: ${MYSQL_DATABASE} WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX} volumes: - wp-content:/var/www/html/wp-content networks: - wp-network

phpmyadmin: image: phpmyadmin:latest container_name: wp-phpmyadmin restart: unless-stopped depends_on: mysql: condition: service_healthy ports: - "8081:80" environment: PMA_HOST: mysql PMA_PORT: ${PMA_PORT} networks: - wp-network

networks: wp-network: driver: bridge

volumes: mysql-data: wp-content: EOF

Bây giờ mình sẽ giải thích chi tiết từng phần.

Service: mysql

  mysql:
    image: mysql:8.0
    container_name: wp-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - wp-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
  • image: mysql:8.0 : Dùng MySQL phiên bản 8.0. Mình chỉ định version cụ thể thay vì latest vì database cần ổn định , upgrade MySQL version có thể gây lỗi data.
  • container_name: wp-mysql: Đặt tên cố định cho container, dễ nhận diện khi chạy docker ps.
  • restart: unless-stopped: Tự restart khi container bị crash hoặc khi Docker/server reboot, trừ khi bạn chủ động docker stop.
  • environment: Các biến môi trường để MySQL tự tạo database và user khi khởi động lần đầu. Giá trị được đọc từ file .env qua cú pháp ${VARIABLE}.
  • volumes: mysql-data:/var/lib/mysql: Mount named volume vào thư mục data của MySQL. Nhờ volume này, dù xoá container rồi tạo lại, data vẫn còn nguyên.
  • networks: wp-network: Kết nối vào custom network để các service khác tìm thấy MySQL qua hostname mysql.
  • healthcheck: Docker sẽ định kỳ chạy mysqladmin ping để kiểm tra MySQL đã sẵn sàng chưa. Nếu MySQL chưa ready, các service phụ thuộc sẽ đợi (nhờ condition: service_healthy). Các thông số: kiểm tra mỗi 10 giây, timeout 5 giây, thử 5 lần, chờ 30 giây ban đầu để MySQL khởi động.

Service: wordpress

  wordpress:
    image: wordpress:latest
    container_name: wp-app
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: mysql:3306
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
      WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX}
    volumes:
      - wp-content:/var/www/html/wp-content
    networks:
      - wp-network
  • image: wordpress:latest: Image chính thức của WordPress trên Docker Hub, bao gồm Apache + PHP + WordPress core.
  • depends_on với condition: service_healthy : WordPress chỉ khởi động sau khi MySQL đã healthy (healthcheck pass). Điều này quan trọng vì WordPress cần kết nối database ngay khi start , nếu MySQL chưa ready, WordPress sẽ lỗi.
  • ports: "8080:80": Map port 8080 trên host vào port 80 trong container. Bạn sẽ truy cập WordPress tại http://IP:8080.
  • WORDPRESS_DB_HOST: mysql:3306 : Trỏ WordPress đến service MySQL. Ở đây mysql là tên service trong Compose , Docker sẽ tự resolve thành IP của container MySQL nhờ internal DNS.
  • volumes: wp-content:/var/www/html/wp-content: Mount named volume cho thư mục wp-content (chứa themes, plugins, uploads). Đảm bảo nội dung upload không mất khi recreate container.

Service: phpmyadmin

  phpmyadmin:
    image: phpmyadmin:latest
    container_name: wp-phpmyadmin
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
    ports:
      - "8081:80"
    environment:
      PMA_HOST: mysql
      PMA_PORT: ${PMA_PORT}
    networks:
      - wp-network
  • image: phpmyadmin:latest: Image chính thức của phpMyAdmin, chạy trên Apache + PHP.
  • depends_on: Tương tự WordPress, đợi MySQL healthy rồi mới start.
  • ports: "8081:80": Truy cập phpMyAdmin tại http://IP:8081.
  • PMA_HOST: mysql: Cho phpMyAdmin biết MySQL server nằm ở đâu (tên service).
  • PMA_PORT: ${PMA_PORT}: Port của MySQL (mặc định 3306).

Networks và Volumes

networks:
  wp-network:
    driver: bridge
volumes:
  mysql-data:
  wp-content:
  • networks: wp-network: Tạo một custom bridge network. Tất cả services trong cùng network có thể giao tiếp qua tên service. Docker Compose tự tạo network mặc định, nhưng mình khai báo tường minh để rõ ràng hơn.
  • volumes: mysql-data, wp-content: Khai báo 2 named volumes. Docker sẽ quản lý nơi lưu trữ trên host. Dữ liệu trong volumes không bị mất khi chạy docker compose down (chỉ mất khi bạn thêm flag -v).

Deploy stack

Mọi thứ đã sẵn sàng. Kiểm tra lại cấu trúc project:

ls -la ~/wordpress-docker/

Bạn sẽ thấy 2 file: docker-compose.yml.env. Giờ deploy:

cd ~/wordpress-docker
docker compose up -d

Lần đầu chạy, Docker sẽ pull 3 images về (mysql, wordpress, phpmyadmin) nên sẽ mất vài phút tuỳ tốc độ mạng. Output sẽ trông như thế này:

[+] Running 5/5
 ✔ Network wordpress-docker_wp-network  Created
 ✔ Volume "wordpress-docker_mysql-data" Created
 ✔ Volume "wordpress-docker_wp-content" Created
 ✔ Container wp-mysql                   Healthy
 ✔ Container wp-app                     Started
 ✔ Container wp-phpmyadmin              Started

Kiểm tra trạng thái các container:

docker compose ps

Output:

NAME             IMAGE               STATUS                   PORTS
wp-mysql         mysql:8.0           Up (healthy)             3306/tcp, 33060/tcp
wp-app           wordpress:latest    Up                       0.0.0.0:8080->80/tcp
wp-phpmyadmin    phpmyadmin:latest   Up                       0.0.0.0:8081->80/tcp

Cả 3 container đều Up, MySQL hiển thị healthy. Stack đã chạy!

Kiểm tra và truy cập

WordPress - http://IP:8080

Mở trình duyệt và truy cập http://<IP-VPS>:8080. Bạn sẽ thấy trang WordPress Setup Wizard: chọn ngôn ngữ, điền thông tin site (tiêu đề, username admin, password, email).

Một số lưu ý:

  • Nếu bạn test trên local: truy cập http://localhost:8080
  • Nếu trên VPS: thay <IP-VPS> bằng IP thực của VPS
  • Nếu không truy cập được: kiểm tra firewall đã mở port 8080 chưa (ufw allow 8080)
  • Đặt password admin WordPress thật mạnh: đây là ứng dụng mở ra internet

phpMyAdmin - http://IP:8081

Truy cập http://<IP-VPS>:8081, bạn sẽ thấy trang đăng nhập phpMyAdmin. Đăng nhập bằng:

  • Username: wpuser (hoặc root nếu cần full quyền)
  • Password: password tương ứng trong file .env

Sau khi đăng nhập, bạn sẽ thấy database wordpress ở panel bên trái. Click vào sẽ thấy các bảng WordPress (wp_posts, wp_users, wp_options,...), đây là toàn bộ dữ liệu trang web của bạn.

⚠️ Lưu ý bảo mật: phpMyAdmin không nên expose ra public trên server production. Trong thực tế, bạn nên giới hạn truy cập bằng IP whitelist, VPN, hoặc chỉ dùng khi cần rồi tắt. Trong bài học này mình mở để tiện thực hành.

Quản lý stack

Stack đã chạy, giờ là lúc học cách quản lý nó hàng ngày.

Xem logs từng service

# Xem log MySQL — kiểm tra khởi động, query errors
docker compose logs mysql

# Xem log WordPress — kiểm tra Apache access/error log docker compose logs wordpress

# Xem log phpMyAdmin docker compose logs phpmyadmin

# Follow log real-time của tất cả services docker compose logs -f

# Follow log của 1 service cụ thể, hiển thị 50 dòng cuối docker compose logs -f --tail 50 wordpress

Mẹo: Khi WordPress lỗi trắng trang hoặc lỗi kết nối database, log là nơi đầu tiên bạn cần kiểm tra. Thường sẽ thấy lỗi rõ ràng như Access denied for user hoặc Connection refused.

Exec vào container

# Exec vào WordPress container
docker compose exec wordpress bash

# Bên trong container, bạn có thể: ls /var/www/html/ # Xem source WordPress cat wp-config.php # Xem config (DB credentials) php -v # Kiểm tra PHP version exit

# Exec vào MySQL container docker compose exec mysql bash

# Hoặc chạy MySQL client trực tiếp docker compose exec mysql mysql -u wpuser -p wordpress # Nhập password khi được hỏi, rồi chạy SQL: # SHOW TABLES; # SELECT * FROM wp_options LIMIT 5; # EXIT;

Restart service

# Restart 1 service cụ thể (không ảnh hưởng services khác)
docker compose restart wordpress

# Restart toàn bộ stack docker compose restart

# Dừng toàn bộ stack (giữ nguyên container) docker compose stop

# Khởi động lại stack đã stop docker compose start

# Dừng + xoá container (data trong volumes vẫn còn) docker compose down

# Chạy lại docker compose up -d

Update image

Khi WordPress hoặc phpMyAdmin có phiên bản mới, cách update rất đơn giản:

# Pull image mới nhất
docker compose pull
# Recreate container với image mới (data vẫn giữ nguyên nhờ volumes)
docker compose up -d

Docker Compose sẽ chỉ recreate những container có image thay đổi, các container khác giữ nguyên. Nếu chỉ muốn update WordPress thôi:

docker compose pull wordpress
docker compose up -d wordpress

⚠️ Lưu ý: Với MySQL, không nên update bừa. Upgrade MySQL version (ví dụ từ 8.0 lên 8.4) có thể cần migration data. Luôn backup trước khi update và đọc release notes.

Backup dữ liệu

Deploy xong mà không có backup thì giống như đi xe mà không mua bảo hiểm. Mình sẽ hướng dẫn backup cả database lẫn file WordPress.

Backup MySQL database

Dùng mysqldump từ bên trong container MySQL:

# Tạo thư mục backup
mkdir -p ~/wordpress-docker/backups
# Backup database ra file SQL
docker compose exec mysql mysqldump -u wpuser -pWpPass@Secure2024 wordpress > ~/wordpress-docker/backups/wordpress-db-$(date +%Y%m%d-%H%M%S).sql

Kiểm tra file backup:

ls -lh ~/wordpress-docker/backups/
# -rw-r--r-- 1 root root 1.2M Mar 14 10:30 wordpress-db-20250314-103045.sql

Để restore backup (khi cần):

docker compose exec -T mysql mysql -u wpuser -pWpPass@Secure2024 wordpress < ~/wordpress-docker/backups/wordpress-db-20250314-103045.sql

Backup wp-content volume

Thư mục wp-content chứa themes, plugins, và ảnh upload. Backup bằng cách copy từ container ra host:

docker compose cp wordpress:/var/www/html/wp-content ~/wordpress-docker/backups/wp-content-$(date +%Y%m%d-%H%M%S)

Hoặc nén lại thành file tar.gz để tiết kiệm dung lượng:

docker compose exec wordpress tar czf - -C /var/www/html wp-content > ~/wordpress-docker/backups/wp-content-$(date +%Y%m%d-%H%M%S).tar.gz

Script backup tự động

Để không phải chạy thủ công mỗi ngày, tạo một script backup đơn giản:

cat > ~/wordpress-docker/backup.sh << 'SCRIPT'
#!/bin/bash
# WordPress Docker Backup Script
# Chạy: bash backup.sh

BACKUP_DIR="$HOME/wordpress-docker/backups" DATE=$(date +%Y%m%d-%H%M%S) COMPOSE_DIR="$HOME/wordpress-docker"

# Tạo thư mục backup mkdir -p "$BACKUP_DIR"

echo "=== WordPress Docker Backup ===" echo "Thời gian: $(date)"

# 1. Backup MySQL database echo "[1/2] Backup database..." cd "$COMPOSE_DIR" docker compose exec -T mysql mysqldump -u wpuser -pWpPass@Secure2024 --single-transaction wordpress > "$BACKUP_DIR/db-$DATE.sql"

if [ $? -eq 0 ]; then echo " ✓ Database backup: db-$DATE.sql ($(du -h "$BACKUP_DIR/db-$DATE.sql" | cut -f1))" else echo " ✗ Database backup FAILED!" fi

# 2. Backup wp-content echo "[2/2] Backup wp-content..." docker compose exec -T wordpress tar czf - -C /var/www/html wp-content > "$BACKUP_DIR/wp-content-$DATE.tar.gz"

if [ $? -eq 0 ]; then echo " ✓ wp-content backup: wp-content-$DATE.tar.gz ($(du -h "$BACKUP_DIR/wp-content-$DATE.tar.gz" | cut -f1))" else echo " ✗ wp-content backup FAILED!" fi

# 3. Xoá backup cũ hơn 7 ngày echo "Dọn backup cũ (>7 ngày)..." find "$BACKUP_DIR" -name "db-*.sql" -mtime +7 -delete find "$BACKUP_DIR" -name "wp-content-*.tar.gz" -mtime +7 -delete

echo "=== Backup hoàn tất ===" SCRIPT

chmod +x ~/wordpress-docker/backup.sh

Chạy backup:

bash ~/wordpress-docker/backup.sh

Để backup tự động hàng ngày, thêm vào crontab:

# Mở crontab
crontab -e
# Thêm dòng này — chạy backup lúc 3:00 AM mỗi ngày
0 3 * * * /bin/bash /root/wordpress-docker/backup.sh >> /root/wordpress-docker/backups/backup.log 2>&1

⚠️ Nhắc nhở: Backup trên cùng server với data thì chưa đủ an toàn. Trong thực tế, bạn nên copy backup sang server khác hoặc cloud storage (S3, Google Drive), nhưng đó là chủ đề ngoài phạm vi bài này.

📚 Serie Docker từ A đến Z

  1. Bài 1: Docker là gì? Tại sao nên dùng Docker trên VPS
  2. Bài 2: Cài đặt Docker và Docker Compose trên VPS Ubuntu
  3. Bài 3: Làm quen với Docker – Các lệnh cơ bản cần biết
  4. Bài 4: Docker Image & Dockerfile – Tự tạo Image riêng
  5. Bài 5: Docker Volume & Network – Quản lý dữ liệu và mạng
  6. Bài 6: Docker Compose là gì? Cài đặt và cú pháp cơ bản
  7. Bài 7: Deploy WordPress + MySQL + phpMyAdmin bằng Docker Compose (đang đọc)
  8. Bài 8: Deploy LEMP Stack (Nginx + PHP-FPM + MariaDB) bằng Docker Compose
  9. Bài 9: Biến môi trường & file .env trong Docker Compose
  10. Bài 10: Reverse Proxy với Nginx Proxy Manager + SSL tự động
  11. Bài 11: Deploy ứng dụng Node.js / Python với Docker Compose
  12. Bài 12: Backup & Restore dữ liệu Docker Volume
  13. Bài 13: Monitoring Docker với Portainer, Uptime Kuma và cAdvisor
  14. Bài 14: Docker Logging – Quản lý log hiệu quả
  15. Bài 15: Bảo mật Docker trên VPS
  16. Bài 16: CI/CD đơn giản – Auto deploy với Webhook + Docker Compose
  17. Bài 17: Docker Compose trong thực tế – Tổng hợp project mẫu

Tổng kết

Trong bài này, bạn đã deploy thành công một stack WordPress hoàn chỉnh với Docker Compose:

Stack 3 services:

  • MySQL 8.0: database với healthcheck, named volume cho persistent data
  • WordPress: web app với depends_on healthy, volume cho wp-content
  • phpMyAdmin: công cụ quản lý database qua web

Best practices đã áp dụng:

  • Tách secrets vào file .env: không hardcode password trong compose file
  • Healthcheck cho MySQL: đảm bảo WordPress chỉ start khi DB sẵn sàng
  • Named volumes: dữ liệu persist qua các lần recreate container
  • Custom network: isolation và service discovery qua tên
  • restart: unless-stopped: tự recovery khi crash
  • Backup script: database + wp-content, tự xoá backup cũ

Lệnh quản lý thường dùng:

  • docker compose up -d: deploy/start stack
  • docker compose ps: xem trạng thái
  • docker compose logs -f [service]: xem log
  • docker compose exec [service] bash: vào container
  • docker compose pull && docker compose up -d: update image
  • docker compose down: dừng stack

Đây là stack đầu tiên bạn deploy "gần production", có database, có backup, có công cụ quản lý. Bạn có thể dùng setup này để chạy blog cá nhân, website thử nghiệm, hoặc làm môi trường dev cho WordPress.

Bài 8, mình sẽ nâng cấp lên một stack phức tạp hơn: LEMP Stack (Linux + Nginx + MySQL + PHP-FPM). Bạn sẽ tự build Nginx config, tách riêng web server và PHP processor, và hiểu cách các ứng dụng PHP thực tế chạy trên production, không phải Apache all-in-one nữa, mà là kiến trúc tách rời, hiệu năng cao hơn.

👈 Bài trước: Docker Compose là gì? Cài đặt và cú pháp cơ bản

👉 Bài tiếp: Deploy LEMP Stack (Nginx + PHP-FPM + MariaDB) bằng Docker Compose

Chia sẻ:
Bài viết đã được kiểm duyệt bởi AZDIGI Team

Về tác giả

Thạch Phạm

Thạch Phạm

Đồng sáng lập và Giám đốc điều hành của AZDIGI. Có hơn 15 năm kinh nghiệm trong phổ biến kiến thức liên quan đến WordPress tại thachpham.com, phát triển website và phát triển hệ thống.

Hơn 10 năm phục vụ 80.000+ khách hàng

Bắt đầu dự án web của bạn với AZDIGI