Skip to content

Buổi 5: Docker Compose

🎯 Mục tiêu

  • Hiểu Docker Compose là gì và tại sao cần dùng
  • Viết file docker-compose.yml chuẩn
  • Quản lý multi-container applications
  • Sử dụng depends_on, profiles, healthcheck
  • Thực hành: Full-stack app (Frontend + Backend + Database)

1. Tại sao cần Docker Compose?

Vấn đề khi dùng docker run

bash
# Phải chạy từng lệnh riêng lẻ, rất dài...
$ docker network create myapp-net

$ docker run -d --name db --network myapp-net \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

$ docker run -d --name redis --network myapp-net \
  redis:7-alpine

$ docker run -d --name api --network myapp-net \
  -e DATABASE_URL=postgres://postgres:secret@db:5432/postgres \
  -e REDIS_URL=redis://redis:6379 \
  -p 3000:3000 \
  my-api

$ docker run -d --name web --network myapp-net \
  -e API_URL=http://api:3000 \
  -p 8080:80 \
  my-frontend

😩 4 container = 4 lệnh dài + 1 lệnh network + phải nhớ thứ tự khởi động.

Giải pháp: Docker Compose

yaml
# docker-compose.yml – MỘT file, MỘT lệnh
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

  api:
    build: ./api
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://postgres:secret@db:5432/postgres
      REDIS_URL: redis://redis:6379
    depends_on:
      - db
      - redis

  web:
    build: ./frontend
    ports:
      - "8080:80"
    depends_on:
      - api

volumes:
  pgdata:
bash
# Một lệnh duy nhất!
$ docker compose up -d

2. Cấu trúc docker-compose.yml

Cấu trúc cơ bản

yaml
# Phiên bản (optional với Compose v2+)
# version: "3.8"  # không cần nữa

# Định nghĩa các services (containers)
services:
  service-name:
    image: image:tag           # Hoặc build từ Dockerfile
    build: ./path              # Build từ Dockerfile
    ports:
      - "host:container"
    environment:
      KEY: value
    volumes:
      - volume:/path
    depends_on:
      - other-service
    restart: unless-stopped

# Định nghĩa volumes
volumes:
  volume-name:

# Định nghĩa networks
networks:
  network-name:

Các trường quan trọng

TrườngÝ nghĩaVí dụ
imageImage từ registrypostgres:16
buildBuild từ Dockerfile./api hoặc {context: ., dockerfile: Dockerfile}
portsPort mapping"3000:3000"
environmentBiến môi trườngNODE_ENV: production
env_fileFile biến môi trường.env
volumesMount volumespgdata:/var/lib/postgresql/data
depends_onThứ tự khởi động[db, redis]
restartChính sách restartunless-stopped
networksChọn networks[frontend, backend]
commandOverride CMDnpm run dev
healthcheckKiểm tra sức khỏetest: curl -f http://localhost

3. Docker Compose CLI

Các lệnh cơ bản

bash
# Khởi động tất cả services (foreground)
$ docker compose up

# Khởi động nền (detached)
$ docker compose up -d

# Build lại images trước khi khởi động
$ docker compose up -d --build

# Dừng tất cả
$ docker compose down

# Dừng và XÓA volumes (⚠️ mất dữ liệu)
$ docker compose down -v

# Xem logs
$ docker compose logs
$ docker compose logs -f api    # follow logs của service "api"

# Xem trạng thái
$ docker compose ps

# Chạy lệnh trong service
$ docker compose exec api bash
$ docker compose exec db psql -U postgres

# Scale service
$ docker compose up -d --scale api=3

# Restart một service
$ docker compose restart api

# Pull images mới nhất
$ docker compose pull

So sánh docker compose up và down

LệnhTạoChạyDừngXóa ContainerXóa NetworkXóa Volume
up -d
stop
down
down -v

4. depends_on nâng cao

depends_on cơ bản

yaml
services:
  api:
    depends_on:
      - db
      - redis

⚠️ depends_on chỉ đảm bảo thứ tự khởi động, KHÔNG đảm bảo service kia đã sẵn sàng.

depends_on với healthcheck

yaml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  api:
    build: ./api
    depends_on:
      db:
        condition: service_healthy
    # API chỉ start KHI db đã healthy ✅

5. Profiles

Tách môi trường dev và production

yaml
services:
  api:
    build: ./api
    ports:
      - "3000:3000"

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret

  # Chỉ chạy khi bật profile "dev"
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    profiles:
      - dev

  # Chỉ chạy khi bật profile "monitoring"
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    profiles:
      - monitoring
bash
# Chạy bình thường (không có adminer, prometheus)
$ docker compose up -d

# Chạy kèm adminer (dev profile)
$ docker compose --profile dev up -d

# Chạy kèm monitoring
$ docker compose --profile monitoring up -d

# Chạy tất cả profiles
$ docker compose --profile dev --profile monitoring up -d

6. Environment variables trong Compose

Các cách truyền biến

yaml
services:
  api:
    image: my-api

    # Cách 1: Inline
    environment:
      NODE_ENV: production
      PORT: 3000

    # Cách 2: Từ file .env
    env_file:
      - .env
      - .env.local

    # Cách 3: Từ biến host (${VAR})
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASSWORD}@db:5432/${DB_NAME}

File .env mặc định

Docker Compose tự động đọc file .env ở cùng thư mục:

env
# .env
DB_PASSWORD=secret
DB_NAME=myapp
API_PORT=3000
yaml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}

  api:
    build: ./api
    ports:
      - "${API_PORT}:3000"

7. Thực hành: Full-stack Application

Kiến trúc

        Browser

     :80   │    :8080
     ┌─────┴──────────────────────┐
     │                            │
     ▼                            ▼
┌─────────┐              ┌────────────┐
│ Frontend│              │  Adminer   │
│ (React) │              │ (DB Admin) │
│  nginx  │              │  profile:  │
│         │              │    dev     │
└────┬────┘              └────────────┘
     │ API calls

┌─────────┐
│   API   │
│ (Node)  │
│ :3000   │
└────┬────┘

     ├──────────────┐
     ▼              ▼
┌─────────┐  ┌───────────┐
│   DB    │  │   Redis   │
│ Postgres│  │  Cache    │
│ :5432   │  │  :6379    │
└─────────┘  └───────────┘

docker-compose.yml

yaml
services:
  # ── Database ──────────────────────────────
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # ── Cache ─────────────────────────────────
  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    restart: unless-stopped

  # ── API Server ────────────────────────────
  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      DATABASE_URL: postgres://app:${DB_PASSWORD:-secret}@db:5432/myapp
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  # ── Frontend ──────────────────────────────
  web:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "80:80"
    depends_on:
      - api
    restart: unless-stopped

  # ── Dev tools (chỉ bật khi cần) ──────────
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    depends_on:
      - db
    profiles:
      - dev

volumes:
  pgdata:

Các lệnh vận hành

bash
# Khởi động production
$ docker compose up -d

# Khởi động dev (kèm adminer)
$ docker compose --profile dev up -d

# Xem logs API
$ docker compose logs -f api

# Truy cập database
$ docker compose exec db psql -U app -d myapp

# Rebuild API sau khi sửa code
$ docker compose up -d --build api

# Dừng toàn bộ
$ docker compose down

# Dừng + xóa data (reset hoàn toàn)
$ docker compose down -v

🏋️ Bài tập thực hành

Bài 1: Compose cơ bản

  1. Tạo file docker-compose.yml với nginx + redis
  2. Chạy docker compose up -d
  3. Kiểm tra docker compose ps
  4. Xem logs docker compose logs
  5. Dừng docker compose down

Bài 2: WordPress stack bằng Compose

  1. Viết docker-compose.yml cho WordPress + MySQL (thay thế các lệnh docker run ở Buổi 4)
  2. Thêm Adminer với profile dev
  3. Chạy và setup WordPress

Bài 3: Healthcheck

  1. Thêm healthcheck cho MySQL service
  2. Cấu hình depends_on với condition: service_healthy
  3. Kiểm tra trạng thái: docker compose ps

Bài 4: Environment variables

  1. Tạo file .env với các biến cấu hình
  2. Sử dụng ${VAR} trong docker-compose.yml
  3. Thử thay đổi giá trị trong .env và restart