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.yamlcho cấu hình nền tảng có thể chia sẻ giữa local và staging. - Dùng
compose.production.yamlcho restart policy, port public, logging và các thay đổi production. - Dùng
env_filehoặ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 -dTên file thường dùng là compose.yaml và compose.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áo | Phù hợp khi | Rủi ro cần kiểm tra |
|---|---|---|
| environment: mapping | Giá trị không nhạy cảm, ít biến, cần thấy ngay trong Compose | Dễ vô tình commit token hoặc password |
| environment: ${VAR} | Cần lấy giá trị từ shell hoặc file env | Thiếu biến có thể làm cấu hình sai |
| env_file | Có nhiều biến theo môi trường, muốn tách khỏi YAML | Khó biết nguồn override nếu có nhiều file |
| secrets | Password, API key, certificate hoặc dữ liệu nhạy cảm | Image 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.txtFile 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: trueTrong 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êu | Cấu hình gợi ý | Ghi chú |
|---|---|---|
| Public qua reverse proxy | Chỉ 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 port | Proxy gọi app:8000 |
| Database | Không publish ra 0.0.0.0 nếu không cần | Chỉ cho app ở backend network |
| Debug tạm thời | Bind 127.0.0.1:host:container | Nhớ 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_healthyHealthcheck 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
- Kiểm tra thay đổi trong Git, Dockerfile, image tag, port, network, volume và env.
- Chạy
docker compose -f compose.yaml -f compose.production.yaml configđể render cấu hình cuối. - Pull hoặc build image với tag bất biến; tránh dùng
latestcho release cần rollback. - Backup database, uploads và các file config trước migration hoặc recreate quan trọng.
- Deploy một service nếu có thể, kiểm tra health endpoint, log, network và resource usage.
- Chạy smoke test từ bên ngoài: DNS, TLS, HTTP status, login, ghi dữ liệu và đọc dữ liệu.
- 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 ls11. 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
- Compose file dùng schema hiện hành, không copy option cũ mà chưa kiểm tra tài liệu.
- Image có tag rõ ràng và đã được kiểm tra nguồn, digest hoặc release note.
- Secret không nằm trong Git, Dockerfile, image layer hoặc log.
- Port public tối thiểu; database và cache chỉ ở network nội bộ.
- Service kết nối bằng DNS service name, không hard-code IP container.
- Volume có tên, có backup và có tài liệu restore.
- Có restart policy, healthcheck, log rotation và monitoring phù hợp.
- Đã 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.