Docker Compose production: App, PostgreSQL, healthcheck và backup
Hướng dẫn thiết kế Docker Compose production với app, PostgreSQL, network, volume, healthcheck, secret, restart và deploy an toàn.
Mục lục bài viết
VPS & Hosting cho dự án của bạn
Khám phá các gói máy chủ và lưu trữ phù hợp từ website cá nhân đến ứng dụng production.
Docker Compose giúp định nghĩa nhiều service, network và volume trong một file YAML. Trên VPS nhỏ, Compose là cách thực tế để chạy API, database và service phụ trợ.
Tuy nhiên, file Compose development không nên được đưa nguyên trạng lên production.
Kiến trúc mẫu
Internet
↓
Nginx trên host :443
↓
API container :3000
↓ private network
PostgreSQL container :5432
Database không public port ra Internet.
Cấu trúc dự án
deployeasy-api/
├── Dockerfile
├── compose.yaml
├── .env
├── .env.example
├── package.json
├── package-lock.json
└── src/
.gitignore:
.env
.env.*
!.env.example
Dockerfile multi-stage
Ví dụ NestJS:
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]
Kiểm tra output thực tế. Monorepo có thể cần copy thêm file.
.dockerignore
node_modules
dist
.git
.env
.env.*
*.log
coverage
Dockerfile*
compose*.yaml
README.md
Không đưa secret và private key vào build context.
Compose production
name: deployeasy
services:
database:
image: postgres:18
restart: unless-stopped
environment:
POSTGRES_DB: ${DATABASE_NAME}
POSTGRES_USER: ${DATABASE_USER}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${DATABASE_USER} -d ${DATABASE_NAME}",
]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
api:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3000
DATABASE_HOST: database
DATABASE_PORT: 5432
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_USER: ${DATABASE_USER}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
depends_on:
database:
condition: service_healthy
ports:
- "127.0.0.1:3000:3000"
networks:
- backend
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://127.0.0.1:3000/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))",
]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
volumes:
postgres-data:
networks:
backend:
driver: bridge
Compose quản lý services, networks và volumes. healthcheck xác định container có healthy hay không.
File .env
DATABASE_NAME=deployeasy
DATABASE_USER=deployeasy
DATABASE_PASSWORD=CHANGE_ME_WITH_A_LONG_RANDOM_VALUE
Permission:
chmod 600 .env
Kiểm tra:
docker compose config
Lệnh có thể hiển thị secret sau khi thay biến; không đăng output công khai.
Vì sao database không có ports?
API truy cập database qua network nội bộ:
database:5432
Không cần:
ports:
- "5432:5432"
Điều này giảm bề mặt tấn công.
Service name là DNS nội bộ
Trong API:
DATABASE_HOST=database
Không dùng:
DATABASE_HOST=localhost
localhost trong API container là chính API container.
depends_on và healthcheck
depends_on:
database:
condition: service_healthy
Compose chờ dependency đạt healthy theo cấu hình được hỗ trợ. Ứng dụng vẫn nên có retry vì database có thể mất kết nối sau khi app đã chạy.
Restart policy
restart: unless-stopped
Các policy thường gặp:
no
always
on-failure
unless-stopped
Restart không chữa lỗi lặp vô hạn. Kiểm tra restart count:
docker inspect \
--format='{{.RestartCount}}' \
CONTAINER_NAME
Resource limit
Tùy phiên bản Compose:
services:
api:
mem_limit: 512m
cpus: 1.0
Kiểm tra:
docker compose config
docker stats
Limit quá thấp gây OOM; quá cao không bảo vệ service khác.
Log rotation
services:
api:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Ứng dụng nên log ra stdout/stderr.
Build và chạy
docker compose config
docker compose build
docker compose up -d
Kiểm tra:
docker compose ps
docker compose logs -f api
Health:
docker inspect \
--format='{{json .State.Health}}' \
CONTAINER_NAME | jq
Nginx trên host
server {
listen 80;
server_name api.deployeasy.vn;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
API chỉ publish localhost:
ports:
- "127.0.0.1:3000:3000"
Migration database
Không để mọi API replica tự chạy migration cùng lúc.
Tạo service:
services:
migrate:
build:
context: .
command: npm run migration:run
environment:
DATABASE_HOST: database
DATABASE_PORT: 5432
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_USER: ${DATABASE_USER}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
depends_on:
database:
condition: service_healthy
networks:
- backend
restart: "no"
Chạy:
docker compose run --rm migrate
Migration production cần:
- Backup.
- Kiểm tra lock.
- Tương thích code cũ/mới.
- Rollback hoặc forward fix.
- Không dùng
synchronize: true.
Backup PostgreSQL
mkdir -p backups
docker compose exec -T database \
pg_dump \
-U "$DATABASE_USER" \
-d "$DATABASE_NAME" \
-Fc \
> "backups/deployeasy-$(date +%F-%H%M%S).dump"
Nếu biến chỉ nằm trong .env, shell host chưa chắc đã export chúng. Có thể dùng script đọc biến một cách kiểm soát.
Backup phải được copy ra nơi khác.
Restore thử nghiệm
docker compose exec database \
createdb -U deployeasy deployeasy_restore_test
docker compose exec -T database \
pg_restore \
-U deployeasy \
-d deployeasy_restore_test \
--clean \
--if-exists \
< backups/file.dump
Không test restore trực tiếp vào production.
Update app
Nếu build trên VPS:
git pull --ff-only
docker compose build api
docker compose run --rm migrate
docker compose up -d --no-deps api
Kiểm tra:
docker compose ps
docker compose logs --tail=200 api
curl -f http://127.0.0.1:3000/health
VPS RAM thấp nên build trong CI rồi pull image.
Dùng image có version
services:
api:
image: ghcr.io/username/deployeasy-api:1.2.3
Deploy:
docker compose pull api
docker compose up -d api
Rollback bằng tag cũ.
Không chỉ dùng latest.
Tránh mất volume
docker compose down
mặc định không xóa named volume.
docker compose down -v
có thể xóa volume và dữ liệu.
Kiểm tra:
docker volume ls
docker compose config --volumes
Dọn Docker
docker system df
docker image prune
docker builder prune
Không chạy docker system prune -a --volumes trên production nếu chưa hiểu tài nguyên sẽ bị xóa.
Secret
- Không commit.
- Không build vào image.
- Hạn chế permission file.
- Tách staging/production.
- Xoay khi lộ.
- Dùng secret manager khi hệ thống lớn.
Security cơ bản
- Chạy app non-root.
- Không mount Docker socket.
- Không dùng
privileged: true. - Chỉ publish port cần thiết.
- Pin image tag.
- Update base image.
- Backup volume.
- Firewall trên host.
Checklist
[ ] Dockerfile multi-stage
[ ] .dockerignore đúng
[ ] Không secret trong image
[ ] App chạy non-root
[ ] Database dùng volume
[ ] Database không public
[ ] Healthcheck app và DB
[ ] Restart policy
[ ] Log rotation
[ ] Migration service riêng
[ ] Backup trước migration
[ ] Restore đã test
[ ] Image có version
[ ] Rollback bằng tag cũ
[ ] Nginx proxy localhost
Câu hỏi thường gặp
Compose có phù hợp production không?
Có thể phù hợp với hệ thống nhỏ trên một host nếu được vận hành cẩn thận. Nó không tự cung cấp high availability đa node.
Xóa container database có mất dữ liệu không?
Nếu named volume còn, dữ liệu thường vẫn còn. Nhưng volume không phải backup.
restart: always có đủ monitoring không?
Không. Container có thể restart liên tục mà không ai biết.
Kết luận
Docker Compose production cần healthcheck, volume, backup, log rotation, version image, migration và rollback; không chỉ docker compose up -d.
Nguồn tham khảo
Đọc tiếp
Bài viết liên quan
Hướng dẫn deploy REMAP bằng Docker lên VPS
Triển khai monorepo REMAP gồm NestJS, Next.js, React và PostgreSQL lên VPS Ubuntu bằng Docker Compose, Nginx, domain và SSL.
Đọc bài →Dockerfile production: 15 cách giảm image, tăng bảo mật
Hướng dẫn viết Dockerfile production với multi-stage build, cache, .dockerignore, non-root user, pin version, healthcheck và secret an toàn.
Đọc bài →Docker là gì? Image, container, volume và Docker Compose
Hướng dẫn Docker từ cơ bản: container hoạt động thế nào, image và container khác gì, Dockerfile, volume, network và Docker Compose.
Đọc bài →