Docker Compose cho người mới: chạy ứng dụng PHP và MySQL cùng lúc chỉ với một lệnh

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Một ứng dụng web thực tế hiếm khi chỉ có một thành phần: thường có máy chủ web và cơ sở dữ liệu. Docker Compose cho phép khai báo cả hai trong một file rồi khởi động bằng một lệnh. Bài này hướng dẫn dựng môi trường PHP và MySQL, rất tiện để phát triển mà không cần cài trực tiếp lên máy.

Compose giải quyết vấn đề gì

Nếu chạy từng container bằng docker run, bạn phải nhớ nhiều tùy chọn: mạng, cổng, biến môi trường, volume. Compose gom tất cả vào file compose.yaml (hoặc docker-compose.yml) và quản lý vòng đời của cả nhóm: tạo mạng riêng, khởi động theo thứ tự, ghi log chung. Đồng đội chỉ cần chạy một lệnh là có môi trường giống bạn. Bài này giả định bạn đã cài Docker Desktop hoặc Docker Engine kèm plugin Compose; kiểm tra bằng docker compose version.

Cấu trúc thư mục

php-mysql-demo/
  compose.yaml
  .env
  app/
    index.php

Bước 1: Viết file compose

services:
  web:
    image: php:8.3-apache
    ports:
      - "8080:80"
    volumes:
      - ./app:/var/www/html
    depends_on:
      db:
        condition: service_healthy
    environment:
      DB_HOST: db
      DB_NAME: ${MYSQL_DATABASE}
      DB_USER: ${MYSQL_USER}
      DB_PASS: ${MYSQL_PASSWORD}

  db:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 5s
      retries: 20

volumes:
  db_data:

Giải thích: mỗi mục trong services là một container. Dịch vụ web dùng image PHP kèm Apache, ánh xạ cổng 8080 của máy sang cổng 80 trong container, và mount thư mục ./app vào thư mục web để sửa code là thấy kết quả ngay. Dịch vụ db dùng MySQL; dữ liệu lưu trong volume tên db_data nên không mất khi xóa container. Trong cùng một file Compose, các dịch vụ tự nằm chung một mạng và gọi nhau bằng tên dịch vụ, vì vậy PHP kết nối tới MySQL qua máy chủ tên db chứ không phải localhost. Phần healthcheck cùng depends_on điều kiện giúp web chỉ khởi động khi MySQL đã sẵn sàng nhận kết nối.

Bước 2: File .env chứa cấu hình

MYSQL_DATABASE=demo
MYSQL_USER=demo_user
MYSQL_PASSWORD=doi_mat_khau_nay
MYSQL_ROOT_PASSWORD=doi_mat_khau_root_nay

Compose tự đọc file .env cùng thư mục để thay thế các biến ${...}. Hãy đổi mật khẩu thành giá trị của bạn và thêm .env vào .gitignore. Có thể commit thêm file mẫu .env.example để đồng đội biết cần những biến nào.

Bước 3: Bật PDO MySQL cho PHP

Image php:8.3-apache mặc định chưa có phần mở rộng pdo_mysql. Tạo file Dockerfile cho dịch vụ web:

FROM php:8.3-apache
RUN docker-php-ext-install pdo pdo_mysql

Rồi trong compose, thay dòng image: php:8.3-apache của dịch vụ web bằng:

    build: .

Bước 4: Trang PHP kiểm tra kết nối

Tạo app/index.php:

<?php
$host = getenv('DB_HOST');
$name = getenv('DB_NAME');
$user = getenv('DB_USER');
$pass = getenv('DB_PASS');

try {
    $pdo = new PDO("mysql:host=$host;dbname=$name;charset=utf8mb4", $user, $pass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    ]);
    $version = $pdo->query('SELECT VERSION()')->fetchColumn();
    echo 'Kết nối thành công. Phiên bản MySQL: ' . htmlspecialchars($version);
} catch (PDOException $e) {
    http_response_code(500);
    echo 'Không kết nối được cơ sở dữ liệu.';
    error_log($e->getMessage());
}

Bước 5: Chạy và quản lý

docker compose up -d --build
docker compose ps
docker compose logs -f web
docker compose exec db mysql -u demo_user -p demo
docker compose down

Mở http://localhost:8080 để thấy thông báo kết nối thành công. Lần đầu MySQL cần thời gian khởi tạo. Lệnh down dừng và xóa container nhưng giữ lại volume dữ liệu; thêm -v chỉ khi bạn thật sự muốn xóa sạch cả dữ liệu.

Lỗi thường gặp

  • Cổng đã bị chiếm: đổi cổng bên trái, ví dụ "8081:80".
  • Đổi mật khẩu trong .env mà MySQL vẫn báo sai: biến môi trường chỉ có tác dụng ở lần khởi tạo đầu tiên của volume. Xóa volume (mất dữ liệu) hoặc đổi mật khẩu bằng lệnh SQL.
  • Kết nối bằng localhost thất bại: trong container phải dùng tên dịch vụ db.
  • Thụt lề YAML sai: dùng dấu cách, không dùng tab.
  • Chạy trên môi trường thật: file này phục vụ phát triển; môi trường chạy thật cần thêm quyền tối thiểu, sao lưu và không phơi cổng MySQL ra ngoài.

Kết luận

Với một file compose ngắn, bạn có môi trường PHP và MySQL tái tạo được ở mọi máy. Cùng cách khai báo này, bạn có thể thêm Redis, phpMyAdmin hay hàng đợi tác vụ chỉ bằng cách thêm một khối vào services.

Để học Docker và triển khai ứng dụng thực tế, bạn có thể tham khảo các khóa học tại codebem.com.

author_photo
Nguyễn Đình Cường

0 comment