Mục lục
- Mục tiêu bài học
- Docker Compose là gì và giới hạn của nó
- Cài đặt và verify
- Cấu trúc file compose.yaml
- Ví dụ AI stack đầy đủ — RAG chatbot
- Service-to-service networking
- Lệnh thường dùng
- Volume: 3 loại
- Environment variables: 4 cách
- Profiles — chạy subset service
- Override file — tách config theo môi trường
- GPU access
- Common pitfalls
- Tóm tắt
- Bài tiếp theo
Mục tiêu bài học
Sau bài này bạn sẽ:
- ✅ Hiểu Docker Compose làm gì và khi nào không dùng được
- ✅ Viết được
compose.yamlcho AI stack (FastAPI + Postgres + Redis + Qdrant) - ✅ Cấu hình healthcheck để service phụ thuộc chờ đúng lúc
- ✅ Quản lý secret qua file
.envkhông commit vào git - ✅ Dùng profiles để tách service dev / monitoring
- ✅ Nhận biết và tránh các pitfall phổ biến
Docker Compose là gì và giới hạn của nó
Docker Compose là tool định nghĩa và chạy nhiều container như một đơn vị thống nhất. Thay vì gõ nhiều lệnh docker run riêng lẻ, bạn khai báo tất cả service trong một file YAML rồi khởi động bằng một lệnh:
docker compose up -d
Compose tự tạo network, mount volume, truyền environment variables, và thiết lập thứ tự khởi động theo depends_on.
Use case phù hợp
- Dev local: chạy toàn bộ dependency (DB, cache, vector DB) giống production mà không cài mỗi thứ riêng trên host.
- CI/CD testing: spin up stack đầy đủ để chạy integration test, rồi teardown.
- Small production (single host): một VPS chạy 3–5 service, traffic vừa phải.
Giới hạn — KHÔNG dùng Compose khi
- Cần scale ngang qua nhiều host — đó là việc của Kubernetes hoặc Docker Swarm.
- Cần rolling deploy zero-downtime với health-gated rollback.
- Service count lên tới hàng chục, cần namespace / RBAC / network policy.
Compose v2 — tên lệnh và tên file thay đổi
Compose v1 là binary riêng (docker-compose, viết bằng Python). Từ Docker Desktop 3.6+ và Docker Engine 20.10+, Compose v2 được tích hợp vào CLI Docker dưới dạng plugin:
# Compose v1 (cũ, không nên dùng)
docker-compose up
# Compose v2 (hiện tại)
docker compose up
Tên file chuẩn cũng thay đổi: từ docker-compose.yml sang compose.yaml. File cũ vẫn work, nhưng compose.yaml là tên được ưu tiên theo Compose Specification.
Cài đặt và verify
macOS / Windows
Docker Desktop bao gồm Compose v2 sẵn. Cài Docker Desktop là đủ.
Linux
# Ubuntu / Debian — cài compose plugin
sudo apt update
sudo apt install docker-compose-plugin
# Hoặc nếu dùng Docker Engine cài tay, thêm plugin:
# Xem hướng dẫn tại docs.docker.com/compose/install/linux/
Verify
docker compose version
# Docker Compose version v2.27.0
Nếu lệnh trả về lỗi "unknown command", bạn đang dùng Docker Engine không có plugin — cài thêm docker-compose-plugin.
Cấu trúc file compose.yaml
Một file compose.yaml tối thiểu gồm ba khối: services, volumes, và (tùy chọn) networks.
services:
api:
build: . # build từ Dockerfile trong thư mục hiện tại
ports:
- "8000:8000" # host_port:container_port
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/app
depends_on:
- db
db:
image: postgres:16 # pull từ Docker Hub
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data # named volume
volumes:
pgdata: # Docker tạo và quản lý volume này
Các trường quan trọng
| Trường | Ý nghĩa |
|---|---|
build |
Đường dẫn đến thư mục chứa Dockerfile (hoặc object {context, dockerfile}) |
image |
Pull image sẵn từ registry thay vì build |
ports |
Publish port ra host. Không khai báo thì service chỉ accessible trong network nội bộ |
environment |
Biến môi trường truyền vào container |
depends_on |
Thứ tự khởi động; kết hợp condition để chờ healthcheck |
volumes |
Mount volume (named hoặc bind mount) |
restart |
no / always / unless-stopped / on-failure |
healthcheck |
Lệnh kiểm tra service đã ready chưa |
Ví dụ AI stack đầy đủ — RAG chatbot
Stack dưới đây chạy bốn service: FastAPI app, Qdrant (vector DB), Redis (session cache), và Postgres (metadata storage). Đây là cấu hình thực tế có thể dùng cho dev local và small single-host production.
# compose.yaml
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- QDRANT_URL=http://qdrant:6333
- REDIS_URL=redis://redis:6379/0
- POSTGRES_URL=postgresql://app:secret@db:5432/ragdb
depends_on:
qdrant:
condition: service_healthy
redis:
condition: service_started
db:
condition: service_healthy
restart: unless-stopped
qdrant:
image: qdrant/qdrant:v1.11.0
ports:
- "6333:6333" # REST API
- "6334:6334" # gRPC
volumes:
- qdrant_data:/qdrant/storage
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6333/healthz"]
interval: 30s
timeout: 5s
retries: 3
redis:
image: redis:7-alpine
command: redis-server --save 60 1 --loglevel warning
volumes:
- redis_data:/data
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: ragdb
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d ragdb"]
interval: 10s
timeout: 5s
retries: 5
volumes:
qdrant_data:
redis_data:
pg_data:
networks:
default:
driver: bridge
Giải thích một số điểm
qdrant/qdrant:v1.11.0: pin version cụ thể, không dùnglatesttrong bất kỳ môi trường nào.latestcó thể pull image khác nhau trên hai máy, gây hành vi không nhất quán.condition: service_healthy: Compose chờ container báohealthytrước khi start service phụ thuộc. Nếu dùngservice_started, container chỉ cần đang chạy — không đảm bảo DB đã accept connection.restart: unless-stopped: tự restart khi container crash, trừ khi bạn dừng thủ công. Thích hợp cho production. Không dùng cho container training — nếu training xong, container exit bình thường,unless-stoppedsẽ không restart.${OPENAI_API_KEY}: giá trị lấy từ shell environment hoặc file.envcùng thư mục. Key không được hardcode vào file YAML.
Service-to-service networking
Tất cả service trong cùng một file compose.yaml tự động nằm trong cùng một Docker network. Docker cung cấp DNS nội bộ: tên service chính là hostname mà các service khác dùng để kết nối.
# Trong code FastAPI, kết nối đến Qdrant:
# ĐÚNG — dùng tên service làm hostname
QDRANT_URL = "http://qdrant:6333"
# SAI — localhost là loopback của container API, không phải container Qdrant
QDRANT_URL = "http://localhost:6333"
Cách DNS nội bộ hoạt động
- Mỗi service có container name mặc định:
<project>-<service>-1(ví dụ:myapp-db-1). - Docker Compose thêm DNS alias bằng tên service (không phải container name) vào network.
- Khi
apilookupqdrant, DNS nội bộ resolve thành IP của containerqdrant.
Port mapping — khi nào cần, khi nào không
Trường ports publish port ra host machine. Nếu service chỉ cần giao tiếp với các service khác trong cùng stack, không cần ports:
services:
db:
image: postgres:16
# Không khai báo ports — DB chỉ accessible từ các service trong stack
# Nếu cần kết nối từ host (pgAdmin, psql), thêm:
# ports:
# - "5432:5432"
Hạn chế expose port ra ngoài sẽ giảm attack surface trong production.
Lệnh thường dùng
Khởi động và dừng stack
# Start toàn bộ stack (foreground, log hiển thị trực tiếp)
docker compose up
# Start ở background (detached)
docker compose up -d
# Rebuild image trước khi start (dùng khi thay đổi code / Dockerfile)
docker compose up --build -d
# Dừng và xóa container (volume giữ nguyên)
docker compose down
# Dừng + xóa container + xóa volume (mất data — cẩn thận)
docker compose down -v
Quan sát và debug
# Xem log của toàn stack (follow)
docker compose logs -f
# Xem log của một service cụ thể
docker compose logs -f api
# Danh sách service và trạng thái
docker compose ps
# Mở shell vào container đang chạy
docker compose exec api bash
# Chạy lệnh một lần (container tạm, xóa sau khi xong)
docker compose run --rm api python manage.py migrate
Quản lý service đơn lẻ
# Restart một service không restart toàn stack
docker compose restart api
# Dừng một service
docker compose stop db
# Start lại service đã dừng
docker compose start db
# Scale service (Compose không có load balancer tích hợp — scale chủ yếu cho test)
docker compose up -d --scale api=2
Volume: 3 loại
1. Named volume
Docker tạo và quản lý ở /var/lib/docker/volumes/. Persist qua các lần docker compose down, chỉ xóa khi dùng -v hoặc docker volume rm.
services:
db:
volumes:
- pg_data:/var/lib/postgresql/data # named volume
volumes:
pg_data: # khai báo ở top-level
Dùng cho: database data, model weights, Qdrant storage — bất kỳ thứ gì cần persist.
2. Bind mount
Mount thư mục từ host vào container. Thay đổi file trên host phản ánh ngay trong container.
services:
api:
volumes:
- ./src:/app/src # code dev — hot reload
- ./data:/app/data # dataset hoặc file cần xử lý
Dùng cho: code khi dev (cùng với hot reload), file config, dataset nhỏ.
Cảnh báo: bind mount . (toàn bộ thư mục project) vào /app sẽ ghi đè site-packages của image, gây ImportError. Chỉ mount thư mục con cụ thể.
3. tmpfs
Mount in-memory, không persist khi container dừng.
services:
api:
tmpfs:
- /tmp
- /app/cache
Dùng cho: file tạm, cache không cần giữ lại, khi cần I/O nhanh và không quan tâm persistence.
Environment variables: 4 cách
Cách 1: Inline trong compose.yaml
services:
api:
environment:
- DEBUG=true
- LOG_LEVEL=info
Dùng cho: giá trị không nhạy cảm, config cố định.
Cách 2: Reference từ shell
services:
api:
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY} # lấy từ shell environment
- PORT=${PORT:-8000} # fallback về 8000 nếu không set
Compose đọc biến từ shell process đang chạy lệnh docker compose up.
Cách 3: File .env tự động (khuyến nghị)
Tạo file .env cùng thư mục với compose.yaml. Compose tự load mà không cần khai báo gì thêm:
# .env
OPENAI_API_KEY=sk-...
POSTGRES_PASSWORD=mysecret
QDRANT_API_KEY=
# compose.yaml — Compose tự thay thế ${OPENAI_API_KEY}
services:
api:
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
Bắt buộc thêm .env vào .gitignore. Cung cấp .env.example với tên biến nhưng giá trị trống để người khác biết cần set gì.
Cách 4: env_file cho nhiều môi trường
services:
api:
env_file:
- .env.base # biến chung
- .env.production # override cho production
Các file sau override biến trùng tên từ file trước. Dùng khi cần tách config theo môi trường rõ ràng hơn.
Profiles — chạy subset service
Profile cho phép nhóm service tùy chọn. Service không có profiles luôn được start. Service có profiles chỉ start khi bạn bật profile tương ứng.
services:
api:
build: .
ports:
- "8000:8000"
# không có profiles — luôn start
db:
image: postgres:16
# không có profiles — luôn start
prometheus:
image: prom/prometheus:v2.52.0
profiles: [monitoring]
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:10.4.2
profiles: [monitoring]
ports:
- "3000:3000"
depends_on:
- prometheus
# Dev thông thường — chỉ start api + db
docker compose up -d
# Khi cần xem metrics
docker compose --profile monitoring up -d
# Tắt toàn bộ kể cả monitoring
docker compose --profile monitoring down
Ngoài monitoring, pattern phổ biến: profile debug cho pgAdmin / RedisInsight, profile worker cho Celery worker chỉ cần trong một số test.
Override file — tách config theo môi trường
Compose merge nhiều file YAML theo thứ tự chỉ định. Key trùng thì file sau thắng; list (ports, volumes, environment) được nối.
# compose.yaml — base config (dùng cả dev lẫn prod)
# compose.override.yaml — tự merge khi không chỉ định -f (dành cho dev local)
# compose.prod.yaml — dùng tường minh cho production
Ví dụ tách dev / prod:
# compose.yaml (base)
services:
api:
build: .
environment:
- LOG_LEVEL=info
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ragdb
volumes:
- pg_data:/var/lib/postgresql/data
volumes:
pg_data:
# compose.override.yaml (dev — tự load khi chạy docker compose up)
services:
api:
build:
target: development # multi-stage target có dev tool
volumes:
- ./src:/app/src # bind mount để hot reload
environment:
- LOG_LEVEL=debug
ports:
- "8000:8000"
db:
ports:
- "5432:5432" # expose ra host để dùng pgAdmin
# compose.prod.yaml (production)
services:
api:
image: registry.example.com/myapp:${IMAGE_TAG}
restart: unless-stopped
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
# Dev (tự merge override)
docker compose up -d
# Production (chỉ dùng base + prod, bỏ qua override)
docker compose -f compose.yaml -f compose.prod.yaml up -d
GPU access
Để container truy cập GPU NVIDIA, host cần:
- NVIDIA driver (driver version ≥ 520 cho CUDA 12.x).
- NVIDIA Container Toolkit:
nvidia-container-toolkitpackage.
Trong compose.yaml, khai báo GPU qua deploy.resources.reservations.devices:
services:
train:
image: pytorch/pytorch:2.3.0-cuda12.1-cudnn8-runtime
command: python train.py
volumes:
- ./:/workspace
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1 # số GPU; dùng "all" để dùng hết
capabilities: [gpu]
# Verify container thấy GPU
docker compose run --rm train nvidia-smi
Một số lưu ý:
count: allkhông giới hạn GPU — container thấy tất cả GPU trên host. Dùngdevice_idsđể pin GPU cụ thể.- Cú pháp
deploy.resourceschỉ work vớidocker compose, không phải Docker Swarm deploy theo nghĩa cluster. - NVIDIA Container Toolkit phải được cài trước khi chạy lệnh; Compose không tự cài driver.
Common pitfalls
1. Dùng localhost thay vì tên service
Lỗi phổ biến nhất. localhost trong container trỏ về loopback của chính container đó, không phải host hay container khác.
# SAI
client = QdrantClient(url="http://localhost:6333")
# ĐÚNG — dùng tên service
client = QdrantClient(url="http://qdrant:6333")
2. depends_on không dùng healthcheck
depends_on: [db] chỉ đảm bảo container db đã start, không đảm bảo Postgres đã accept connection. App có thể fail với connection refused trong vài giây đầu.
# Thiếu — app có thể crash trước khi DB ready
depends_on:
- db
# Đúng — chờ đến khi DB healthy
depends_on:
db:
condition: service_healthy
Kết hợp với healthcheck trên service db (xem bước 5).
3. Bind mount ghi đè site-packages
# SAI — ghi đè toàn bộ /app kể cả site-packages đã cài trong image
volumes:
- .:/app
# ĐÚNG — chỉ mount thư mục source code
volumes:
- ./src:/app/src
4. docker compose down -v trong production
Flag -v xóa named volume — mất toàn bộ data database. Chỉ dùng khi cần reset môi trường hoàn toàn (dev, CI). Trong production, không bao giờ chạy down -v mà không backup trước.
5. Secret hardcode trong compose.yaml
# SAI — commit lên git → lộ secret
environment:
- OPENAI_API_KEY=sk-abc123xyz...
# ĐÚNG — đọc từ .env (không commit)
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
6. Build chậm vì không dùng BuildKit
Docker BuildKit tăng tốc build đáng kể nhờ parallel build, layer caching thông minh. Bật bằng biến môi trường hoặc cấu hình Docker daemon:
DOCKER_BUILDKIT=1 docker compose up --build
# Hoặc đặt trong .env để luôn bật
Docker Desktop 4.x và Docker Engine 23+ bật BuildKit mặc định.
7. Network conflict giữa nhiều stack
Hai stack khác nhau đều dùng default network với cùng subnet có thể xung đột. Đặt tên network rõ ràng:
networks:
default:
name: myapp_network
driver: bridge
Tóm tắt
- ✅
compose.yamllà tên file chuẩn của Compose v2; lệnh làdocker compose(không có dấu gạch ngang) - ✅ Service giao tiếp nhau qua tên service làm hostname — không dùng
localhost - ✅
depends_onvớicondition: service_healthyđảm bảo DB sẵn sàng trước khi app start - ✅ Named volume persist data; bind mount dùng cho code dev; tmpfs cho cache in-memory
- ✅ Secret qua
.env+.gitignore, không hardcode trong YAML - ✅ Profiles tách service tùy chọn (monitoring, debug) khỏi service bắt buộc
- ✅ Override file tách config dev / production mà không duplicate toàn bộ YAML
- ✅ GPU access qua
deploy.resources.reservations.devices+ NVIDIA Container Toolkit
