Docker Compose Profiles: Quản lý dịch vụ tùy chọn theo môi trường — debug, monitoring tách biệt hoàn toàn

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Vấn đề thực tế: File docker-compose.yml ngày càng phình to

Khoảng 6 tháng trước, mình đang maintain một dự án e-commerce với khoảng 8 microservices. File docker-compose.yml lúc đó trông khá ổn — cho đến khi team thêm Prometheus, Grafana, Jaeger để làm observability, rồi thêm Mailhog để test email, thêm pgAdmin để xem database…

Hậu quả? File docker-compose lên đến 300 dòng. Mỗi lần deploy lên staging, mình phải comment tay hàng loạt service không cần thiết. Có lần quên uncomment lại — Grafana chạy thẳng lên production mà không ai hay. RAM server tăng 40% chỉ vì mấy công cụ monitoring chạy không cần thiết.

Chưa kể hôm phát hiện memory leak trong container: mất đúng 2 ngày debug. Một phần lý do là môi trường local và staging không đồng nhất — local chạy thêm Jaeger tracing, staging không có. Log tracing không match, cứ lần theo hướng sai mãi.

Đó là lúc mình đào lên Docker Compose Profiles — tính năng có từ Compose v1.28 nhưng ít người để ý dùng.

Root cause: Nhét tất cả vào một file là sai từ thiết kế

Chúng ta cố dùng một file docker-compose.yml duy nhất cho bốn hoàn cảnh hoàn toàn khác nhau:

  • Local development: cần hot-reload, debug port, mail catcher, DB admin UI
  • CI/CD testing: chỉ cần app + database, chạy test xong là xóa
  • Staging: gần giống production, có thêm monitoring
  • Production: chỉ service thật, không có gì thừa

Nhiều người giải quyết bằng nhiều file: docker-compose.override.yml, docker-compose.prod.yml… Cách này được. Nhưng số lượng service tăng lên là bắt đầu rối — phải nhớ merge thứ tự, dễ sinh lỗi config chồng lấp.

Các cách giải quyết đã thử

Cách 1: Nhiều file Compose + merge thủ công

# Chạy với nhiều file
docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d

# Hoặc dùng override
docker compose -f docker-compose.yml -f docker-compose.override.yml up -d

Hoạt động, nhưng mệt. Mỗi developer phải nhớ câu lệnh dài. CI/CD script hardcode. Thêm môi trường mới là thêm file, thêm script — nhân lên không ngừng.

Cách 2: Biến môi trường để bật/tắt service

Một số người dùng trick với biến môi trường và extends, nhưng Compose không hỗ trợ bật/tắt service theo điều kiện một cách native. Kết quả là code hacky, người mới vào team đọc không hiểu nổi.

Cách gọn nhất: Docker Compose Profiles

Profiles là tính năng chính thức, sinh ra đúng cho bài toán này. Mỗi service được gán một hoặc nhiều profile. Khi chạy, bạn khai báo profile nào được kích hoạt — chỉ service thuộc profile đó (hoặc không có profile) mới khởi động.

Cách dùng Docker Compose Profiles thực tế

Cấu trúc cơ bản

# docker-compose.yml
version: '3.9'

services:
  # Service chính — không có profile = luôn chạy
  app:
    image: myapp:latest
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production

  postgres:
    image: postgres:15
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

  # --- Chỉ chạy khi bật profile "debug" ---
  mailhog:
    image: mailhog/mailhog
    profiles: ["debug"]
    ports:
      - "8025:8025"

  pgadmin:
    image: dpage/pgadmin4
    profiles: ["debug"]
    ports:
      - "5050:80"
    environment:
      - [email protected]
      - PGADMIN_DEFAULT_PASSWORD=admin

  # --- Chỉ chạy khi bật profile "monitoring" ---
  prometheus:
    image: prom/prometheus
    profiles: ["monitoring"]
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana
    profiles: ["monitoring"]
    ports:
      - "3001:3000"

  # --- Chỉ dùng cho CI testing ---
  test-runner:
    image: myapp:test
    profiles: ["test"]
    command: npm test
    depends_on:
      - postgres
      - redis

volumes:
  pgdata:

Chạy theo từng môi trường

# Production: chỉ app + postgres + redis
docker compose up -d

# Local development: thêm mailhog + pgadmin
docker compose --profile debug up -d

# Staging với monitoring:
docker compose --profile monitoring up -d

# Cần cả debug lẫn monitoring cùng lúc:
docker compose --profile debug --profile monitoring up -d

# CI/CD chạy test:
docker compose --profile test run --rm test-runner

Ngoài flag --profile, còn có cách set qua biến môi trường — tiện hơn khi dùng với file .env:

# Dùng biến môi trường COMPOSE_PROFILES
export COMPOSE_PROFILES=debug,monitoring
docker compose up -d

# Hoặc đặt trong file .env
echo "COMPOSE_PROFILES=debug" >> .env

Một service có thể thuộc nhiều profile

  jaeger:
    image: jaegertracing/all-in-one
    # Chạy khi bật debug HOẶC monitoring
    profiles: ["debug", "monitoring"]
    ports:
      - "16686:16686"
      - "4317:4317"

Kiểm tra service nào thuộc profile nào

# Xem tất cả service và profile của chúng
docker compose config --services

# Xem config đã render đầy đủ
docker compose config

# List service sẽ chạy khi bật profile "debug"
docker compose --profile debug config --services

Cấu trúc thực tế mình dùng cho dự án e-commerce

version: '3.9'

services:
  api:
    build: ./api
    environment:
      - DATABASE_URL=postgresql://user:pass@postgres:5432/ecom
      - REDIS_URL=redis://redis:6379
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:15
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=ecom
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass

  redis:
    image: redis:7-alpine

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl

  # === DEBUG TOOLS ===
  pgadmin:
    image: dpage/pgadmin4
    profiles: ["debug"]
    ports:
      - "5050:80"

  mailhog:
    image: mailhog/mailhog
    profiles: ["debug"]
    ports:
      - "8025:8025"

  # Override api để bật debug mode
  api-dev:
    build:
      context: ./api
      target: development
    profiles: ["debug"]
    volumes:
      - ./api:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - DEBUG=*
    ports:
      - "9229:9229"  # Node.js debug port

  # === MONITORING ===
  prometheus:
    image: prom/prometheus
    profiles: ["monitoring"]
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana
    profiles: ["monitoring"]
    ports:
      - "3001:3000"
    volumes:
      - grafana_data:/var/lib/grafana

  cadvisor:
    image: gcr.io/cadvisor/cadvisor
    profiles: ["monitoring"]
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro

volumes:
  pgdata:
  grafana_data:

Tích hợp vào workflow thực tế

File .env cho từng môi trường

# .env.local
COMPOSE_PROFILES=debug
NODE_ENV=development

# .env.staging  
COMPOSE_PROFILES=monitoring
NODE_ENV=staging

# .env.production
COMPOSE_PROFILES=
NODE_ENV=production
# Deploy theo môi trường
docker compose --env-file .env.local up -d
docker compose --env-file .env.staging up -d
docker compose --env-file .env.production up -d

Makefile đơn giản hóa câu lệnh

up-dev:
	docker compose --profile debug up -d

up-staging:
	docker compose --profile monitoring up -d

up-prod:
	docker compose up -d

test:
	docker compose --profile test run --rm test-runner

down:
	docker compose --profile debug --profile monitoring --profile test down

Một điểm dễ quên: Khi chạy docker compose down mà không chỉ định profile, Compose chỉ dừng service không có profile. Muốn dừng tất cả, phải liệt kê đủ profile — hoặc dùng flag --profiles "*" từ Compose v2.20+:

# Dừng tất cả service kể cả có profile
docker compose --profiles "*" down

Kết quả sau 6 tháng dùng thực tế

Sau khi migrate sang Profiles, file docker-compose.yml gọn hơn rõ rệt — không còn đống comment block tứ tung. Developer trong team không còn vô tình bật monitoring tools lên production, vì chỉ cần không set COMPOSE_PROFILES là các service đó không tồn tại với Compose.

Vụ memory leak mình kể ở đầu? Nếu hồi đó đã dùng Profiles, Jaeger sẽ chạy đồng nhất ở cả local lẫn staging cùng một profile debug. Log tracing khớp nhau, và mình đã không mất 2 ngày đuổi theo hướng sai.

Profiles không giải quyết được mọi thứ — bài toán secrets management, config per-environment vẫn cần công cụ khác. Nhưng với team 3 người trở lên chạy nhiều môi trường, đây là cách tổ chức rõ ràng nhất mình từng dùng trong Docker Compose. Thử một lần là khó quay lại cách cũ.

Share: