Cài đặt Docker Compose trên VPS: chạy 10 dịch vụ với 1 file cấu hình

VPS mới thuê về, bạn bắt đầu "nghi lễ" quen thuộc: cài Nginx, cài PHP, cài MySQL, cấu hình từng thứ một — mất nửa ngày, và 3 tháng sau không ai nhớ đã cấu hình gì, phiên bản nào. Server thứ hai làm lại từ đầu, sai một dòng config là toi cả buổi debug. Docker Compose trên VPS chấm dứt cảnh đó: toàn b

Cài đặt Docker Compose trên VPS: chạy 10 dịch vụ với 1 file cấu hình

VPS mới thuê về, bạn bắt đầu "nghi lễ" quen thuộc: cài Nginx, cài PHP, cài MySQL, cấu hình từng thứ một — mất nửa ngày, và 3 tháng sau không ai nhớ đã cấu hình gì, phiên bản nào. Server thứ hai làm lại từ đầu, sai một dòng config là toi cả buổi debug. Docker Compose trên VPS chấm dứt cảnh đó: toàn bộ 10 dịch vụ — web, database, cache, queue — được định nghĩa trong một file docker-compose.yml duy nhất. Dựng server mới chỉ cần copy file sang và chạy một lệnh.

Bài viết hướng dẫn chi tiết cài đặt Docker Compose trên VPS từ con số 0: cài Docker, viết file compose chạy 10 dịch vụ phổ biến, các mẹo production và checklist bảo mật mà nhiều người bỏ qua.

Docker compose trên vps: docker Compose là gì? Vì sao nên dùng trên VPS?

Docker đóng gói mỗi ứng dụng cùng môi trường của nó thành container — một "hộp" độc lập, chạy giống hệt nhau trên mọi máy. Docker Compose là công cụ đi kèm, cho phép bạn định nghĩa nhiều container liên quan trong một file YAML duy nhất (docker-compose.yml), rồi quản lý chúng bằng vài lệnh đơn giản: up (chạy), down (dừng), logs (xem log), pull (cập nhật image).

Vì sao đây là "chân ái" cho VPS của SME:

Tái lập môi trường trong 5 phút. File compose chính là tài liệu sống của hệ thống: ai đọc cũng biết đang chạy những gì, phiên bản nào, cấu hình ra sao. VPS hỏng? Thuê VPS mới, copy file sang, docker compose up -d — xong.

Cô lập dịch vụ, hết xung đột. PHP 8.1 cho web cũ và PHP 8.3 cho web mới chạy song song, mỗi thứ một container — không còn cảnh "nâng PHP thì web kia sập". MySQL, Redis, Elasticsearch mỗi dịch vụ một container riêng biệt.

Nâng cấp an toàn. Muốn lên MySQL 8.1? Sửa một dòng version trong file, chạy lại — nếu lỗi, về lại version cũ trong 1 phút. Không còn cảnh "nâng cấp xong không rollback được".

Dọn dẹp triệt để. Xóa một dịch vụ là xóa sạch container + image + (tùy chọn) volume — không còn file rác, service ma như khi cài trực tiếp trên OS.

Cài đặt Docker và Docker Compose trên VPS

Hướng dẫn cho Ubuntu 22.04/24.04 (phổ biến nhất trên VPS Việt Nam). Toàn bộ khoảng 10 phút.

Bước 1: Cập nhật hệ thống và cài đặt phụ thuộc.

sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg

Bước 2: Thêm kho chính thức của Docker.

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Bước 3: Cài Docker Engine + Compose plugin.

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

Bước 4: Kiểm tra và cấu hình chạy không cần sudo.

sudo docker run hello-world   # phải in ra "Hello from Docker!"
sudo usermod -aG docker $USER # đăng xuất/đăng nhập lại để có hiệu lực
docker compose version        # kiểm tra: Docker Compose v2.x

Lưu ý: Docker Compose v2 dùng lệnh docker compose (không gạch ngang), là plugin chính thức. Lệnh cũ docker-compose (v1, Python) đã ngừng phát triển — đừng cài theo hướng dẫn cũ trên mạng.

Bước 5: Bật Docker khởi động cùng hệ thống (mặc định đã bật sau khi cài từ kho chính thức — kiểm tra bằng sudo systemctl is-enabled docker).

Docker compose trên vps: file docker-compose.yml mẫu: 10 dịch vụ phổ biến

Dưới đây là file mẫu chạy một stack web hoàn chỉnh. Đừng copy-paste mù quáng — đọc chú thích để hiểu rồi điều chỉnh theo nhu cầu.

version: "3.8"

services:
  # 1. Reverse proxy - cửa ngõ duy nhất ra Internet
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - web
    restart: unless-stopped

  # 2. Web app chính (ví dụ PHP)
  web:
    image: php:8.2-fpm-alpine
    volumes:
      - ./app:/var/www/html
    environment:
      - DB_HOST=db
      - REDIS_HOST=redis
    restart: unless-stopped

  # 3. Database chính
  db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db_root
      - MYSQL_DATABASE=appdb
    volumes:
      - db_data:/var/lib/mysql
    secrets:
      - db_root
    restart: unless-stopped

  # 4. Cache
  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASS}
    volumes:
      - redis_data:/data
    restart: unless-stopped

  # 5. Hàng đợi job nền
  queue:
    image: php:8.2-cli-alpine
    volumes:
      - ./app:/var/www/html
    command: php /var/www/html/worker.php
    depends_on:
      - redis
      - db
    restart: unless-stopped

  # 6. Quản trị DB bằng web
  phpmyadmin:
    image: phpmyadmin:5.2
    environment:
      - PMA_HOST=db
    ports:
      - "127.0.0.1:8080:80"  # chỉ mở local, không public!
    restart: unless-stopped

  # 7. Giám sát container
  portainer:
    image: portainer/portainer-ce:2.19
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - portainer_data:/data
    ports:
      - "127.0.0.1:9000:9000"
    restart: unless-stopped

  # 8. Thu thập log tập trung
  # 9. Backup database tự động
  db-backup:
    image: databack/mysql-backup:8.0
    environment:
      - DB_DUMP_TARGET=/backup
      - DB_DUMP_CRON=0 2 * * *
    volumes:
      - ./backups:/backup
      - db_data:/var/lib/mysql:ro
    depends_on:
      - db
    restart: unless-stopped

  # 10. Certbot - SSL miễn phí tự động
  certbot:
    image: certbot/certbot:v2.6
    volumes:
      - ./nginx/ssl:/etc/letsencrypt
      - ./nginx/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done'"

volumes:
  db_data:
  redis_data:
  portainer_data:

secrets:
  db_root:
    file: ./secrets/db_root_password.txt

Giải thích các quyết định quan trọng trong file mẫu:

  • Chỉ nginx mở cổng ra Internet (80/443). Mọi dịch vụ khác giao tiếp qua mạng nội bộ Docker — database không bao giờ lộ ra ngoài.
  • Secrets thay vì password trần: mật khẩu root DB nằm trong file riêng ./secrets/, không viết trực tiếp vào compose (tránh lọt lên git).
  • Volumes đặt tên (db_data) để Docker quản lý — dữ liệu sống sót khi xóa/tạo lại container. Xóa volume là mất dữ liệu — thao tác nguy hiểm nhất cần cẩn trọng.
  • restart: unless-stopped — container tự chạy lại khi VPS reboot. Đây là dòng nhiều người quên nhất.
  • phpMyAdmin/Portainer bind 127.0.0.1 — chỉ truy cập qua SSH tunnel, không public.

Stack 10 dịch vụ trong một file Docker Compose

Docker compose trên vps: các lệnh Docker Compose dùng hàng ngày

Lệnh Tác dụng Khi dùng
docker compose up -d Chạy toàn bộ stack (nền) Lần đầu / sau khi sửa file
docker compose down Dừng và xóa container (giữ volume) Bảo trì, nâng cấp
docker compose ps Xem trạng thái các service Kiểm tra nhanh
docker compose logs -f web Xem log realtime của service web Debug
docker compose pull && docker compose up -d Cập nhật image mới nhất Nâng cấp phiên bản
docker compose exec db bash Mở shell trong container db Thao tác trực tiếp
docker compose restart nginx Khởi động lại 1 service Sau khi sửa config nginx

Quy trình nâng cấp an toàn: pull image mới → up -d service đó → kiểm tra log/health → lỗi thì sửa version về cũ và up -d lại. Cả quá trình thường dưới 2 phút downtime (với nginx reload thì gần như zero-downtime).

Backup trước khi thay đổi lớn: docker compose down không xóa volume nên dữ liệu an toàn — nhưng với thay đổi rủi ro cao (nâng major version DB), hãy backup volume trước: docker run --rm -v db_data:/data -v $(pwd)/backups:/backup alpine tar czf /backup/db_data.tar.gz /data.

Quản lý dữ liệu: volume và backup

Đây là phần quyết định giữa "chạy chơi" và "chạy production":

Phân biệt 3 loại lưu trữ: - Named volume (db_data): Docker quản lý, nằm ở /var/lib/docker/volumes/ — dùng cho database, redis. Sống sót khi recreate container. - Bind mount (./app:/var/www/html): thư mục thật trên host — dùng cho code, config để dễ sửa trực tiếp. - tmpfs: lưu trên RAM, mất khi container dừng — dùng cho cache tạm, session.

Backup database tự động: service db-backup trong file mẫu dump MySQL mỗi đêm 2h ra ./backups/. Cộng thêm backup volume định kỳ (tar toàn bộ volume 1 lần/tuần) và đồng bộ bản backup ra ngoài VPS (rclone lên S3/cloud hoặc scp về máy khác). Backup nằm cùng VPS với dữ liệu thì chưa gọi là backup — cháy VPS là mất cả hai.

Kiểm thử restore: mỗi tháng một lần, dựng stack từ file compose + bản backup trên VPS/máy test, kiểm tra web chạy được không. File compose làm việc này cực dễ — đó chính là giá trị lớn nhất của nó.

Checklist bảo mật cho Docker trên VPS

Docker mặc định "mở" hơn nhiều người tưởng. Checklist tối thiểu:

1. Không expose Docker socket ra ngoài. Portainer mount /var/run/docker.sock là cần thiết — nhưng đừng bao giờ mở cổng của nó ra Internet. Dùng SSH tunnel: ssh -L 9000:localhost:9000 user@vps.

2. Chạy container với user không phải root khi có thể. Thêm user: "1000:1000" vào service nếu image hỗ trợ. Container bị tấn công mà chạy root thì kẻ tấn công có nhiều "đồ chơi" hơn.

3. Cập nhật image thường xuyên. Image cũ = CVE cũ. Đặt lịch pull + up -d hàng tháng cho các service không quan trọng; service quan trọng thì test trên môi trường staging trước.

4. Quét lỗ hổng image. Dùng docker scout (tích hợp sẵn bản mới) hoặc Trivy miễn phí: trivy image mysql:8.0 — biết image mình dùng có CVE nghiêm trọng nào không.

5. Giới hạn tài nguyên container. Một container lỗi (memory leak) có thể "ăn" hết RAM VPS làm sập cả stack. Đặt deploy.resources.limits: memory: 2G cho từng service quan trọng.

6. Firewall trên host vẫn cần. Docker tự thêm iptables rules khi publish cổng — nhưng UFW/firewalld trên host vẫn nên bật để kiểm soát SSH và các cổng khác. Lưu ý: Docker và UFW có xung đột lịch sử — kiểm tra kỹ rule sau khi bật.

7. Không commit secret vào git. File .env và thư mục secrets/ phải nằm trong .gitignore. Dùng Docker secrets hoặc biến môi trường khi deploy.

Checklist bảo mật container Docker

Tối ưu hiệu năng trên VPS nhỏ

VPS 2–4 GB RAM chạy 10 container? Được, nếu tối ưu:

Chọn image Alpine. nginx:1.25-alpine (~40 MB) thay vì nginx:1.25 (~190 MB). Nhân với 10 service, tiết kiệm hàng trăm MB RAM.

Tắt service không dùng. Đừng chạy đủ 10 service vì "cho oai". Không cần phpMyAdmin thường xuyên? Comment lại, khi cần thì up riêng.

MySQL tinh chỉnh cho RAM thấp. Thêm vào service db: command: --innodb-buffer-pool-size=512M --max-connections=50 (cho VPS 4 GB). Mặc định MySQL "ngốn" RAM theo cấu hình server lớn.

Log rotation. Container log mặc định không giới hạn — một service lỗi spam log có thể đầy ổ đĩa. Cấu hình trong /etc/docker/daemon.json: "log-opts": {"max-size": "10m", "max-file": "3"}.

Theo dõi tài nguyên: docker stats xem realtime; cài thêm cAdvisor hoặc dùng Portainer để có biểu đồ. Khi RAM thường xuyên trên 85%, đã đến lúc nâng cấp VPS thay vì cố tối ưu.

Ví dụ thực tế: chạy WordPress bằng Docker Compose

Để thấy sức mạnh của Compose, đây là file hoàn chỉnh chạy WordPress production-ready — chỉ 30 dòng thay vì nửa ngày cài LEMP thủ công:

services:
  wordpress:
    image: wordpress:6.4-php8.2-apache
    volumes:
      - wp_data:/var/www/html
    environment:
      - WORDPRESS_DB_HOST=db
      - WORDPRESS_DB_NAME=wpdb
      - WORDPRESS_DB_USER=wpuser
      - WORDPRESS_DB_PASSWORD_FILE=/run/secrets/wp_db
    secrets:
      - wp_db
    restart: unless-stopped

  db:
    image: mariadb:11.2
    volumes:
      - db_data:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=wpdb
      - MYSQL_USER=wpuser
      - MYSQL_PASSWORD_FILE=/run/secrets/wp_db
      - MYSQL_RANDOM_ROOT_PASSWORD=1
    secrets:
      - wp_db
    restart: unless-stopped

  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/wp.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - wordpress
    restart: unless-stopped

volumes:
  wp_data:
  db_data:

secrets:
  wp_db:
    file: ./secrets/wp_db_password.txt

Triển khai: docker compose up -d, mở trình duyệt vào IP VPS — trang cài đặt WordPress hiện ra. Muốn chuyển host? Copy 3 thứ (file compose, thư mục secrets, volume backup) sang VPS mới là xong. Muốn nâng PHP? Đổi tag image wordpress, up -d lại — xong trong 1 phút.

Kết luận

Docker Compose trên VPS biến việc quản trị server từ "nghề thủ công" thành "kỹ thuật có tài liệu": mọi dịch vụ trong một file, dựng lại trong 5 phút, nâng cấp không sợ, backup có quy trình. Với SME và team nhỏ, đây là cách hiệu quả nhất để chạy nhiều dịch vụ ổn định trên một VPS mà không cần đội DevOps.

Bắt đầu ngay hôm nay: cài Docker theo 5 bước trên, viết file compose cho 2–3 dịch vụ đầu tiên, rồi mở rộng dần. Khi đã quen, bạn sẽ không bao giờ muốn quay lại cảnh cài từng thứ bằng tay.

Để chạy Docker Compose mượt mà, VPS cần CPU ổn định và I/O đĩa tốt — SunCloud cung cấp Cloud Server dùng ổ SSD NVMe hiệu năng cao, phù hợp chạy container production, kèm dùng thử 10 ngày để bạn kiểm chứng trước khi gắn bó lâu dài. Liên hệ hotline 083 979 3434 hoặc truy cập suncloud.vn để được tư vấn cấu hình.

Câu hỏi thường gặp

Docker Compose khác gì Docker Swarm/Kubernetes?

Compose quản lý multi-container trên một máy — đơn giản, đủ cho SME. Swarm/Kubernetes điều phối container trên nhiều máy (cluster), phức tạp hơn nhiều. Quy tắc: một VPS → Compose; nhiều server cần HA/auto-scale → mới nghĩ đến Kubernetes.

Nên dùng docker compose hay docker-compose?

Dùng docker compose (v2, plugin chính thức của Docker). docker-compose v1 (Python) đã ngừng phát triển từ 2023. Các hướng dẫn cũ trên mạng dùng v1 — hãy chuyển sang cú pháp v2.

Dữ liệu trong container có mất khi update image không?

Không, nếu dùng volume đúng cách. docker compose up -d sau khi pull chỉ thay container, volume (nơi chứa dữ liệu) được giữ nguyên. Mất dữ liệu chỉ xảy ra khi bạn xóa volume (docker compose down -v — lệnh nguy hiểm, đọc kỹ trước khi dùng).

Chạy 10 container trên VPS 2GB RAM được không?

Được nếu chọn image Alpine, tinh chỉnh MySQL và tắt service không cần thiết. Nhưng production nghiêm túc nên dùng VPS từ 4GB RAM trở lên để có khoảng thở cho traffic tăng đột biến.

Làm sao backup toàn bộ stack Docker?

Hai lớp: (1) backup dữ liệu — dump database hàng ngày + tar volume hàng tuần, đồng bộ ra ngoài VPS; (2) backup cấu hình — file docker-compose.yml, .env, thư mục config đưa lên git (riêng tư, không chứa secret). Có hai thứ này là dựng lại được toàn bộ ở bất cứ đâu.

Container có an toàn hơn cài trực tiếp không?

An toàn hơn ở khía cạnh cô lập (lỗi một service khó lan sang service khác và host), nhưng không phải "miễn nhiễm". Vẫn cần cập nhật image, quét CVE, cấu hình firewall và không chạy container với quyền thừa — như checklist bảo mật trong bài.

Tác giả:
author avatar
Tôi là Trường Phạm, hiện đảm nhiệm vai trò CEO của SunCloud. Với kiến ​​thức sâu rộng về mạng lưới hệ thống, điện toán đám mây và an ninh mạng, góp phần định hướng chiến lược phát triển bền vững cho doanh nghiệp. Tôi đã điều hành SunCloud phát triển và vận hành thành công hàng loạt dự án thực tiễn, mang lại giá trị thực tế cho các doanh nghiệp và cơ sở kinh doanh. Bản thân tôi đã đạt được các chứng chỉ quốc tế uy tín như CCNP, LPI, VCP và không ngừng học hỏi để nâng cao năng lực quản lý cũng như chuyên môn mỗi ngày. Đam mê công nghệ và khao khát cống hiến, tôi luôn nỗ lực xây dựng SunCloud trở thành một hệ sinh thái công nghệ đáng tin cậy, đồng thời lan tỏa những giá trị tri thức hữu ích đến cộng đồng.

Tin tức nổi bật
Tin tức khác