jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Docker for Developers · Part 6 — Compose in Depth: Env, Profiles, Healthchecks & Scaling

Đào sâu Compose với interpolation, healthcheck, profiles, override files, scale và restart policy qua các lab có thể chạy.

Đây là Phần 6 của chặng nền tảng về Docker → Compose → Kubernetes. Ở Phần 5 bạn đã khai báo stack nhiều dịch vụ trong compose.yaml và bật bằng docker compose up. Mỗi phần kết thúc bằng bài tập; hãy làm, đừng chỉ đọc.

compose up chỉ làm container khởi động — “đã start” không đồng nghĩa sẵn sàng. Production bug hay bắt đầu ở khoảng trống đó: web start trước database, biến env không resolve như bạn nghĩ, service debug vô tình chạy ở mọi môi trường. Phần này làm file Compose vững và linh hoạt: nội suy env, phụ thuộc theo health, profile tùy chọn, override file, scale và chính sách restart.


Biến môi trường & nội suy biến

Compose đọc file .env ở root project (cùng thư mục với compose.yaml) và dùng giá trị đó khi parse YAML — trước khi container chạy. Cú pháp ${VAR} thay thế lúc parse; ${VAR:-default} dùng giá trị mặc định khi chưa set.

# compose.yaml
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
    ports:
      - "${DB_PORT:-5432}:5432"
# .env  (project root — do NOT commit secrets; add .env to .gitignore)
POSTGRES_PASSWORD=devpass
DB_PORT=5433

env_file khác: nó đưa cặp key/value vào môi trường container lúc runtime — không nội suy file compose.

env_file nạp vào container; environment vẫn nội suy trong YAML. Giữ secret ngoài image và git — .env local, secret CI trên prod (Phần 3).

Quy tắc kiểm tra trước khi blame app: chạy docker compose config. Nó cho bạn file YAML đã được resolve sau khi Compose đọc .env, merge override và nội suy biến. Nếu config resolved sai, runtime không có cơ hội chạy đúng.


Healthcheck & điều kiện depends_on

Container có thể đang chạy trong khi Postgres vẫn đang khởi tạo — race kinh điển từ depends_on dạng ngắn ở Phần 5. Sửa bằng healthcheck trên dịch vụ phụ thuộc và condition: service_healthy trên dịch vụ cần chờ.

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -q"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 10s

  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    depends_on:
      db:
        condition: service_healthy
FieldVai trò
testLệnh exit 0 = healthy
intervalTần suất chạy test
timeoutThời gian chờ tối đa mỗi lần test
retriesSố lần fail trước khi đánh unhealthy
start_periodKhoảng ân hạn — fail chưa tính
  TIME ─────────────────────────────────────────────────────────────►

  db container starts

       ├──── start_period (10s) ────┤  failures ignored here
       │                            │
       │         pg_isready polls every interval
       │                            │
       │                            ▼
       │                    status: healthy
       │                            │
       └────────────────────────────┼──► web may start (service_healthy)

  without healthcheck: web starts here ──X── (DB may still be init)
docker compose up -d && docker compose ps   # HEALTH: starting → healthy

Healthcheck tốt phải trả lời câu hỏi “service này có nhận việc an toàn chưa?”, không chỉ “process còn sống không?”. Với database, pg_isready tốt hơn kiểm tra PID. Với API, /ready thường nên kiểm tra dependency tối thiểu; /healthz chỉ nên rẻ và ổn định. Kubernetes sẽ tách rõ hai khái niệm này thành readiness/liveness ở Phần 10.


Profile — dịch vụ tùy chọn

Không phải ai cũng cần Mailhog, Adminer hay sidecar debug mỗi ngày. Đánh dấu dịch vụ tùy chọn bằng profiles — chúng tắt trừ khi bạn bật.

services:
  api:
    image: myapp:latest
    # no profile → always starts with `docker compose up`

  mailhog:
    image: mailhog/mailhog
    profiles: [debug]
    ports:
      - "8025:8025"
docker compose up -d                        # api only
docker compose --profile debug up -d        # api + mailhog

Dùng profile cho công cụ chỉ dev, job import một lần, hoặc dependency nặng không muốn mỗi lần up.


Nhiều file compose & override

Compose gộp nhiều file thành một spec hiệu lực:

  1. stack cơ sở.
  2. tự load trên cùng máy để chỉnh local (bind mount, cổng debug).
  3. file bổ sung tường minh, vd overlay prod.
docker compose up -d
# equivalent to merging compose.yaml + compose.override.yaml if override exists

docker compose -f compose.yaml -f compose.prod.yaml up -d

Quy tắc gộp (rút gọn): file sau ghi đè scalar và nối list khi được phép; tên service là khóa gộp — cùng tên thì merge sâu từng key. map như environment, labels thường merge theo key.

compose.override.yaml thường thêm bind mount và cổng debug cho api trên laptop.


Scale dịch vụ

Chạy nhiều container cùng một service mà không nhân đôi YAML:

docker compose up -d --scale worker=3
docker compose ps    # three worker containers

Hợp worker stateless (consumer hàng đợi, batch) trên mạng nội bộ không gán cổng host cố định cho từng replica. Không ổn khi mỗi replica cùng ports: "8080:80" — chỉ một bind được cổng host. Để scale HTTP trên một host, đặt reverse proxy hoặc load balancer phía trước; trên cluster production thường scale bằng Kubernetes (Phần 9+) thay vì --scale.


Chính sách restart

Khi daemon hoặc host reboot, Compose có tự bật lại dịch vụ?

services:
  api:
    restart: unless-stopped   # restart unless you explicitly stopped it
  worker:
    restart: on-failure

Thường dùng: no, unless-stopped (daemon local), on-failure (worker).


Các bẫy thường gặp

  • Lệnh healthcheck không có trong imagepg_isready phải có trong image Postgres; app FROM scratch không có curl trừ khi bạn cài.
  • depends_on dạng ngắn chỉ sắp thứ tự startdepends_on: [db] không chờ healthy; dùng condition: service_healthy.
  • .env vs env_file.env cho nội suy Compose; env_file set env container. Có thể dùng cả hai; đừng lẫn tầng nào thấy biến nào.
  • Scale dịch vụ đã publish cổng — bind cổng host trùng sẽ fail; scale worker nội bộ, không scale frontend có ports.
  • Commit .env chứa secret — thêm .env vào .gitignore; commit .env.example với giá trị giả.

Bảng tra nhanh

# env & profiles
docker compose config              # resolved YAML after interpolation
docker compose --profile debug up -d

# health
docker compose ps                  # HEALTH column
docker compose up -d --wait        # wait for running/healthy; verify with `docker compose version`

# overrides & scale
docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose up -d --scale worker=3

# lifecycle
docker compose restart api
docker compose down -v

Pattern cần nhớ: healthcheck, service_healthy, profile, restart, và nội suy biến có kiểm chứng bằng config.


Bài tập / Exercises

Làm trong compose-lab/; dọn bằng docker compose down -v. Các bài mở rộng cùng compose.yaml.

1. Thêm .env, nội suy, xác nhận bằng docker compose config.

Lời giải
# .env
POSTGRES_PASSWORD=exercise
WEB_PORT=8080
# compose.yaml — db + web
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
  web:
    image: nginx:alpine
    ports: ["${WEB_PORT}:80"]
docker compose config | grep -E 'POSTGRES_PASSWORD|"8080:80"'
docker compose up -d && curl -sI http://localhost:8080 | head -n 1

2. Thêm healthcheck + service_healthy; docker compose ps đến khi db healthy.

Lời giải
# under db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -q"]
      interval: 3s
      timeout: 3s
      retries: 10
      start_period: 5s
# under web:
    depends_on:
      db: { condition: service_healthy }
docker compose up -d && docker compose ps

3. Thêm mailhog profile debug; chứng minh up mặc định bỏ qua.

Lời giải
  mailhog:
    image: mailhog/mailhog
    profiles: [debug]
    ports: ["8025:8025"]
docker compose down && docker compose up -d && docker compose ps    # no mailhog
docker compose --profile debug up -d && docker compose ps           # mailhog up

4. Thêm worker, scale 3.

Lời giải
  worker:
    image: alpine:3.24
    command: sleep infinity
docker compose up -d --scale worker=3
docker compose ps | grep worker

5. Override bind-mount html/, xác nhận trang merge.

Lời giải
mkdir -p html && echo '<h1>override works</h1>' > html/index.html
# compose.override.yaml
services:
  web:
    volumes: ["./html:/usr/share/nginx/html:ro"]
docker compose up -d && curl -s http://localhost:8080/

Nâng cao: compose.prod.yaml + restart; so sánh config với hai file -f.

Lời giải
services:
  web: { restart: unless-stopped, volumes: [] }
  db: { restart: unless-stopped }
docker compose -f compose.yaml -f compose.prod.yaml config

Điểm chính

  • .env nội suy file compose; env_file nạp env container — dùng ${VAR:-default} và không nhúng secret vào image.
  • healthcheck + depends_on: condition: service_healthy vá khoảng trống “DB đang chạy nhưng chưa sẵn sàng” từ Phần 5.
  • profiles giữ dịch vụ dev tùy chọn ngoài luồng up mặc định.
  • compose.override.yaml-f xếp tầng môi trường mà không nhân đôi cả stack.
  • --scale hợp worker stateless; tránh scale dịch vụ publish cùng cổng host.

Tiếp theo

Phần 7 — Tối ưu & bảo mật image: thu nhỏ image, tăng tốc build, quét lỗ hổng và chạy container với quyền tối thiểu — bước cứng hóa production.