Docker Compose production: tổ chức file, env và network đúng cách

Hướng dẫn tổ chức Docker Compose production với file override, env, secrets, network, healthcheck, logging và quy trình deploy an toàn trên VPS Linux.

NHT
· 9 phút đọc
Docker Compose production: tổ chức file, env và network đúng cách

Docker Compose rất tiện khi dựng môi trường local, nhưng file chạy được trên máy developer chưa chắc đã sẵn sàng cho production. Ở môi trường thật, vấn đề không chỉ là viết đúng YAML. Bạn còn phải quyết định file nào thuộc base configuration, giá trị nào được đưa qua env, secret nằm ở đâu, service nào được public, network nào cần cô lập và container sẽ được cập nhật ra sao.

Bài này đưa ra một cấu trúc thực dụng cho Docker Compose production trên một VPS hoặc máy chủ đơn lẻ. Mục tiêu là dễ đọc, dễ validate, ít lộ credential và có thể triển khai lặp lại. Nếu bạn mới làm quen Docker, hãy đọc thêm bài Docker là gì và cách container hoạt động trước khi áp dụng các ví dụ bên dưới.

1. Trả lời nhanh: Compose production nên tổ chức thế nào?

Một cấu trúc Compose production an toàn thường có file base mô tả service dùng chung, file override production mô tả khác biệt khi chạy thật, file env không commit secret, network được chia theo vai trò và volume được đặt tên rõ ràng. Trước khi chạy, hãy render cấu hình cuối cùng, kiểm tra image, port, quyền truy cập và kế hoạch backup.

  • Dùng compose.yaml cho cấu hình nền tảng có thể chia sẻ giữa local và staging.
  • Dùng compose.production.yaml cho restart policy, port public, logging và các thay đổi production.
  • Dùng env_file hoặc interpolation có kiểm soát cho cấu hình môi trường; không commit mật khẩu.
  • Dùng Compose secrets cho password, API key hoặc certificate cần cấp theo từng service.
  • Dùng service name để kết nối nội bộ, không hard-code IP container.
Production không phải là dev chạy thêm cờ -d; đó là một cấu hình có chủ đích về quyền, dữ liệu, mạng và khả năng phục hồi.Nguyên tắc triển khai Docker thực dụng của TechNT.

2. Base file và production override khác nhau ra sao?

Docker Docs khuyến nghị có thể dùng một định nghĩa Compose cho nhiều môi trường, sau đó thêm file production chỉ chứa những thay đổi cần thiết. Khi chạy nhiều file bằng tuỳ chọn -f, Compose merge chúng theo thứ tự. File sau có thể bổ sung hoặc override giá trị của file trước.

# Kiểm tra cấu hình merge trước khi chạy
Docker compose -f compose.yaml -f compose.production.yaml config

# Chạy stack production ở background
docker compose -f compose.yaml -f compose.production.yaml up -d

Tên file thường dùng là compose.yamlcompose.production.yaml. Bạn không nhất thiết phải tách mọi thứ thành nhiều file nếu ứng dụng nhỏ, nhưng tách base/production giúp giảm việc copy-paste và làm rõ phần nào chỉ dành cho máy thật.

Ví dụ file base

services:
  web:
    build: .
    depends_on:
      - db
    environment:
      APP_ENV: ${APP_ENV:-development}
      DB_HOST: db
      DB_PORT: "5432"
    networks:
      - frontend
      - backend

  db:
    image: postgres:16
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - backend

volumes:
  db_data:

networks:
  frontend:
  backend:

Ví dụ phần override production

services:
  web:
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8000"
    env_file:
      - ./env/production.env

  db:
    restart: unless-stopped
    expose:
      - "5432"

3. Tổ chức thư mục Compose để tránh nhầm env và secret

Một repository dễ vận hành nên tách file Compose, Dockerfile, script migration và dữ liệu bí mật thành các vùng rõ ràng. Thư mục env/ có thể tồn tại trên server nhưng không đưa vào Git; còn file mẫu chỉ chứa tên biến và giá trị giả.

project/
├── compose.yaml
├── compose.production.yaml
├── Dockerfile
├── .dockerignore
├── env/
│   ├── production.env.example
│   └── production.env          # không commit
├── secrets/                    # quyền filesystem chặt
├── docker/
│   └── entrypoint.sh
└── app/

Đừng nhầm file .env dùng cho interpolation của Compose với env_file dùng để nạp biến vào container. Hai cơ chế có thể cùng xuất hiện, nhưng cần ghi rõ nguồn giá trị để tránh việc một biến có giá trị khác với điều bạn nhìn thấy trong file.

4. Environment variable: nên dùng environment hay env_file?

Compose hỗ trợ khai báo biến bằng mapping hoặc list trong thuộc tính environment. Bạn cũng có thể dùng env_file để tách cấu hình khỏi file YAML. Khi cần truyền giá trị hiện có từ shell, Compose cho phép viết tên biến không có giá trị; khi muốn cảnh báo nếu biến thiếu, dùng interpolation như ${DEBUG}.

Cách khai báoPhù hợp khiRủi ro cần kiểm tra
environment: mappingGiá trị không nhạy cảm, ít biến, cần thấy ngay trong ComposeDễ vô tình commit token hoặc password
environment: ${VAR}Cần lấy giá trị từ shell hoặc file envThiếu biến có thể làm cấu hình sai
env_fileCó nhiều biến theo môi trường, muốn tách khỏi YAMLKhó biết nguồn override nếu có nhiều file
secretsPassword, API key, certificate hoặc dữ liệu nhạy cảmImage phải biết đọc file secret, cần phân quyền đúng

Theo Docker Docs, không nên dùng environment variable để truyền dữ liệu nhạy cảm vì biến có thể bị lộ qua process, log hoặc thao tác debug. Với credential, nên dùng Compose secrets và chỉ cấp quyền cho service thực sự cần.

5. Secrets trong Compose: không để password trong YAML

Compose secrets được mount thành file tại /run/secrets/<secret_name>. Secret phải được định nghĩa ở cấp top-level và được khai báo lại dưới từng service. Cơ chế này tạo ranh giới rõ hơn so với việc đưa mọi password vào environment.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

File secret cần có quyền filesystem phù hợp và không nên nằm trong thư mục public hoặc được đưa vào image bằng Dockerfile. Hãy bổ sung secrets/ vào .gitignore, kiểm tra lịch sử Git và dùng cơ chế rotate riêng nếu credential đã từng bị commit.

6. Network: dùng service name, không dùng IP container

Compose mặc định tạo một bridge network cho project. Các service cùng network có thể tìm nhau bằng service name qua DNS nội bộ. Ví dụ, ứng dụng kết nối PostgreSQL bằng db:5432, không phải IP được nhìn thấy từ một lần docker inspect.

services:
  proxy:
    image: nginx:alpine
    networks:
      - frontend

  app:
    image: ghcr.io/example/app:1.4.0
    networks:
      - frontend
      - backend

  db:
    image: postgres:16
    networks:
      - backend

networks:
  frontend:
  backend:
    internal: true

Trong ví dụ này, proxy chỉ nói chuyện với app; app nói chuyện được với cả proxy và database; database không được đặt trực tiếp trên mạng frontend. Thuộc tính internal: true phù hợp cho network không cần kết nối ra ngoài, nhưng hãy nhớ một service có nhiều network vẫn có thể ra Internet qua network không internal.

7. Port public và expose khác nhau thế nào?

Thuộc tính ports publish cổng container ra host, vì vậy có thể làm service xuất hiện trên Internet nếu firewall và reverse proxy cho phép. Thuộc tính expose chỉ mô tả cổng dùng cho giao tiếp nội bộ và không publish trực tiếp ra host.

Mục tiêuCấu hình gợi ýGhi chú
Public qua reverse proxyChỉ publish Nginx/Caddy; app bind 127.0.0.1 hoặc chỉ ở network nội bộGiảm bề mặt tấn công
Service nội bộKhông dùng ports; dùng service name và container portProxy gọi app:8000
DatabaseKhông publish ra 0.0.0.0 nếu không cầnChỉ cho app ở backend network
Debug tạm thờiBind 127.0.0.1:host:containerNhớ gỡ sau khi kiểm tra

Sai lầm phổ biến là viết 5432:5432 cho database production chỉ vì muốn tiện debug. Nếu database không cần truy cập từ ngoài host, hãy bỏ mapping và dùng network nội bộ. Nếu bắt buộc mở, giới hạn địa chỉ bind, firewall và danh sách client được phép.

8. Restart, healthcheck và thứ tự khởi động

Restart policy giúp container tự khởi động lại sau lỗi hoặc reboot, nhưng không thay thế healthcheck, monitoring và xử lý nguyên nhân. depends_on giúp thể hiện quan hệ khởi động, song không đảm bảo database đã sẵn sàng nhận query nếu bạn không có healthcheck hoặc logic retry ở ứng dụng.

services:
  db:
    image: postgres:16
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    image: ghcr.io/example/app:1.4.0
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy

Healthcheck phải phản ánh khả năng phục vụ thực tế, không chỉ kiểm tra process còn tồn tại. Với web app, có thể kiểm tra endpoint health nhẹ; với worker, kiểm tra tiến trình hoặc cơ chế heartbeat. Hãy tránh healthcheck quá nặng vì nó chạy lặp lại trên production.

9. Logging và cập nhật image trong production

Production cần biết log đi đâu, giữ trong bao lâu và ai được đọc. Nếu chỉ dùng docker logs mà không có giới hạn, log có thể chiếm disk. Tuỳ quy mô, bạn có thể cấu hình driver/log rotation hoặc chuyển log đến hệ thống tập trung.

services:
  web:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"

Khi cập nhật code hoặc image, hãy rebuild/pull image rồi recreate đúng service. Docker Docs minh họa việc build lại service và chạy docker compose up --no-deps -d web để không recreate dependency không liên quan. Trước khi đổi image, ghi lại tag, digest, migration và phương án rollback.

10. Quy trình validate và deploy an toàn

  1. Kiểm tra thay đổi trong Git, Dockerfile, image tag, port, network, volume và env.
  2. Chạy docker compose -f compose.yaml -f compose.production.yaml config để render cấu hình cuối.
  3. Pull hoặc build image với tag bất biến; tránh dùng latest cho release cần rollback.
  4. Backup database, uploads và các file config trước migration hoặc recreate quan trọng.
  5. Deploy một service nếu có thể, kiểm tra health endpoint, log, network và resource usage.
  6. Chạy smoke test từ bên ngoài: DNS, TLS, HTTP status, login, ghi dữ liệu và đọc dữ liệu.
  7. Ghi version image, thời điểm deploy và lệnh rollback vào changelog hoặc runbook.
# Một số lệnh kiểm tra sau deploy
docker compose ps
docker compose logs --tail=100 web
docker inspect $(docker compose ps -q web) --format '{{json .State.Health}}'
docker network ls
docker volume ls

11. Các lỗi Compose production thường gặp

  • Commit file .env có password: xóa khỏi Git không làm credential cũ an toàn; phải rotate secret.
  • Dùng IP container: IP đổi sau recreate; hãy dùng service name.
  • Publish database ra Internet: bỏ ports nếu không cần và đặt database ở backend network.
  • Mount source code ở production: code có thể bị sửa từ host; dùng image bất biến cho app.
  • Không giới hạn log: disk đầy làm app hoặc database gặp lỗi ngoài dự kiến.
  • Dùng docker compose down -v: có thể xóa named volume; luôn kiểm tra lệnh trước khi chạy.
  • Chỉ kiểm tra container đang Up: process sống không đồng nghĩa endpoint hoạt động.

12. Checklist file Compose trước khi đưa lên VPS

  1. Compose file dùng schema hiện hành, không copy option cũ mà chưa kiểm tra tài liệu.
  2. Image có tag rõ ràng và đã được kiểm tra nguồn, digest hoặc release note.
  3. Secret không nằm trong Git, Dockerfile, image layer hoặc log.
  4. Port public tối thiểu; database và cache chỉ ở network nội bộ.
  5. Service kết nối bằng DNS service name, không hard-code IP container.
  6. Volume có tên, có backup và có tài liệu restore.
  7. Có restart policy, healthcheck, log rotation và monitoring phù hợp.
  8. Đã chạy config validation và có lệnh rollback được thử trên staging.

13. Kết luận

Docker Compose production tốt là một hệ thống cấu hình có thể đọc, kiểm tra và khôi phục. Hãy tách base và production khi cần, giữ env không nhạy cảm ở đúng chỗ, dùng secrets cho credential, phân lớp network và chỉ publish service thực sự cần public. Sau đó bổ sung healthcheck, restart, logging, backup và runbook rollback.

Nếu bạn đang triển khai Docker lần đầu, hãy bắt đầu từ stack nhỏ: reverse proxy, app và database. Khi cấu trúc đã ổn, mới thêm worker, queue, monitoring hoặc service phụ. Cách này dễ debug hơn nhiều so với việc đưa toàn bộ hệ thống vào một file Compose lớn ngay từ đầu.

Tài liệu tham khảo

  1. Docker Docs — Use Compose in production.
  2. Docker Docs — Set environment variables within your container.
  3. Docker Docs — Networking in Compose.
  4. Docker Docs — Manage secrets securely in Docker Compose.
  5. Docker Docs — Compose file reference.