Tuần 1 - Ngày 4: Docker Compose
Mục tiêu học tập
- Hiểu vấn đề Docker Compose giải quyết khi chạy multi-container app
- Nắm cú pháp
docker-compose.yml(compose spec) - Hiểu networking mặc định giữa các services
- Dùng volumes để persist data
- Quản lý environment variables và secrets trong Compose
- Xây dựng full example app: web + Postgres + Redis
1. Tại sao cần Docker Compose?
Không có Compose, chạy app 3 containers cần 3 lệnh dài:
# Không có Compose — lệnh phức tạp, dễ sai
docker network create myapp-net
docker run -d \
--name postgres \
--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 web \
--network myapp-net \
-p 3000:3000 \
-e DATABASE_URL=postgres://postgres:secret@postgres:5432/mydb \
-e REDIS_URL=redis://redis:6379 \
myapp:latest
Với Compose, tất cả trong một file:
docker compose up -d
2. Cú pháp docker-compose.yml
Cấu trúc tổng quan
# docker-compose.yml
services: # Các containers
web: # Tên service
...
db:
...
volumes: # Named volumes
pgdata:
networks: # Custom networks (tuỳ chọn)
backend:
Lưu ý phiên bản: Compose V2 (tích hợp sẵn Docker Desktop 2022+) dùng
docker compose(không có dấu gạch nối).version:field ở đầu file đã deprecated — bỏ qua.
Service definition chi tiết
services:
web:
# 1. Image hoặc build
image: nginx:1.27 # dùng image có sẵn
# hoặc
build:
context: . # build context
dockerfile: Dockerfile # Dockerfile path
target: runtime # multi-stage target
# 2. Đặt tên container
container_name: my-web
# 3. Port mapping
ports:
- "8080:80" # host:container
- "443:443"
# 4. Environment variables
environment:
NODE_ENV: production
PORT: "3000"
# hoặc từ file
env_file:
- .env
- .env.local
# 5. Volumes
volumes:
- ./src:/app/src # bind mount (dev — sync code real-time)
- pgdata:/var/lib/postgresql # named volume (persist data)
- /tmp:/tmp:ro # read-only mount
# 6. Networks
networks:
- frontend
- backend
# 7. Phụ thuộc vào service khác
depends_on:
db:
condition: service_healthy # đợi healthcheck pass
# 8. Healthcheck
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# 9. Restart policy
restart: unless-stopped # always | on-failure | unless-stopped | no
# 10. Resource limits
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
3. Networking trong Compose
Default network
Compose tự tạo một bridge network và attach tất cả services vào đó. Các services giao tiếp với nhau qua tên service (DNS resolution).
# web service có thể connect đến db bằng hostname "db"
environment:
DATABASE_URL: "postgres://user:pass@db:5432/mydb"
# ^^
# Tên service trong Compose
Custom networks (phân tách frontend/backend)
services:
nginx:
networks: [frontend]
web:
networks: [frontend, backend]
db:
networks: [backend] # Không truy cập được từ nginx
networks:
frontend:
backend:
internal: true # Không có internet access
4. Volumes — Persist data
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data # Named volume — persist qua restart/recreate
web:
volumes:
- ./src:/app/src # Bind mount — sync code từ host (dev)
- uploads:/app/uploads # Named volume cho user uploads
volumes:
pgdata: # Docker quản lý, lưu trong /var/lib/docker/volumes/
uploads:
Phân biệt bind mount vs named volume:
| Tiêu chí | Bind mount | Named volume |
|---|---|---|
| Path | Đường dẫn cụ thể trên host | Docker quản lý |
| Dùng cho | Dev (sync source code) | Production data (DB, uploads) |
| Dễ backup | Dễ (biết path) | Cần docker volume commands |
| Hiệu năng | Host filesystem | Tốt hơn trên Docker Desktop/Mac |
5. Full example: Web App + Postgres + Redis
Cấu trúc dự án
.env.example (commit vào git)
# .env.example — copy thành .env và điền giá trị thật
POSTGRES_USER=myapp
POSTGRES_PASSWORD=changeme
POSTGRES_DB=myapp_db
REDIS_PASSWORD=changeme
APP_SECRET_KEY=changeme
docker-compose.yml (production-like)
docker-compose.override.yml (dev — không commit)
# Override cho môi trường development
# Compose tự động merge file này với docker-compose.yml
services:
web:
build:
target: builder # dùng stage builder (có devDependencies)
volumes:
- ./src:/app/src # sync code real-time (bind mount)
environment:
NODE_ENV: development
DEBUG: "true"
command: ["npm", "run", "dev"] # hot reload
db:
ports:
- "5432:5432" # expose DB ra host để dùng TablePlus/DBeaver
redis:
ports:
- "6379:6379"
6. Các lệnh Compose thường dùng
# Khởi động tất cả services (detached)
docker compose up -d
# Chỉ khởi động một số services
docker compose up -d db redis
# Build lại images trước khi up
docker compose up -d --build
# Xem logs
docker compose logs -f
docker compose logs -f web # chỉ service web
# Xem status
docker compose ps
# Exec vào service
docker compose exec web bash
docker compose exec db psql -U myapp -d myapp_db
# Dừng (giữ containers)
docker compose stop
# Dừng và xoá containers (giữ volumes)
docker compose down
# Xoá cả volumes (XOÁ DATA)
docker compose down -v
# Restart một service sau khi sửa code
docker compose restart web
# Scale một service
docker compose up -d --scale web=3
# Chạy một-lần (không restart)
docker compose run --rm web npm run migrate
7. Environment variables — thứ tự ưu tiên
Cao nhất → Thấp nhất
1. docker compose run -e VAR=val (CLI flag)
2. Shell environment của user (export VAR=val trước khi chạy)
3. docker-compose.yml environment: (inline trong compose file)
4. .env file (trong cùng thư mục)
5. Dockerfile ENV instruction (thấp nhất)
# Debug: xem tất cả env vars của service
docker compose config # in ra compose config đã interpolate
docker compose exec web env # in env vars trong container
Câu hỏi ôn tập
-
Hai services trong cùng Compose file giao tiếp với nhau bằng gì (không phải IP)?
Xem đáp án
Bằng tên service làm hostname. Compose tự tạo một bridge network và register DNS cho mỗi service. Service
webcó thể connect đến servicedbbằng hostnamedb(port 5432), hoặcredisbằng hostnameredis— không cần biết IP động. -
Sự khác biệt giữa bind mount và named volume là gì?
Xem đáp án
Bind mount map một đường dẫn cụ thể trên host vào container — phù hợp cho development (sync source code real-time). Named volume do Docker quản lý ở
/var/lib/docker/volumes/— phù hợp cho production data (DB, uploads) vì persist quadocker compose downvà có hiệu năng tốt hơn trên Docker Desktop/Mac.Bind mount phụ thuộc vào cấu trúc thư mục host; named volume portable hơn.
-
Lệnh nào để dừng và xoá containers nhưng giữ lại data volumes?
Xem đáp án
docker compose down(không có flag-v). Lệnh này dừng và xoá containers, networks được tạo bởi Compose, nhưng giữ lại named volumes. Thêm-v(docker compose down -v) mới xoá volumes — cẩn thận vì xoá cả data database. -
docker compose up -d --buildkhác gìdocker compose up -d?Xem đáp án
--buildbuộc Compose rebuild image từ Dockerfile trước khi up, dù image đã tồn tại. Nếu không có--build, Compose dùng image đã cached (có thể là phiên bản cũ nếu code đã thay đổi). Dùng--buildsau khi sửa Dockerfile hoặc source code để đảm bảo container chạy code mới nhất. -
Tại sao
.envkhông nên commit vào git nhưng.env.examplethì nên?Xem đáp án
.envchứa giá trị thật của secrets (passwords, API keys) — commit vào git nghĩa là lịch sử git lưu secrets vĩnh viễn, ngay cả khi xoá file sau này..env.examplechứa key template với giá trị placeholder (không có secret thật), giúp developer mới biết cần set những biến gì mà không lộ thông tin nhạy cảm.
Bài tập thực hành
# Tạo thư mục dự án
mkdir compose-demo && cd compose-demo
# Tạo file .env
cat > .env << 'EOF'
POSTGRES_USER=demo
POSTGRES_PASSWORD=demopass
POSTGRES_DB=demodb
EOF
# Tạo docker-compose.yml
cat > docker-compose.yml << 'EOF'
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:
EOF
# Khởi động
docker compose up -d
# Kiểm tra status
docker compose ps
# Kết nối vào Postgres
docker compose exec db psql -U demo -d demodb -c "\l"
# Xem logs
docker compose logs db
# Dọn dẹp
docker compose down -v # -v xoá cả volume
Tài liệu tham khảo chính thức
Tiếp theo: Ngày 5 — ECR và Deploy lên ECS Fargate