Thiết lập Live Reload và Hot Reload khi phát triển với Docker Compose
Hướng dẫn Live Reload và Hot Reload với Docker Compose cho Node.js, TypeScript, Vite và Next.js: bind mounts, Compose Watch, node_modules, polling, quyền file và debug.
ShanverseCập nhật 11 phút đọc— lượt xemĐang tải lượt xem
Docker
Docker Compose
Live Reload, Hot Reload và Process Restart khác nhau thế nào?
Với backend Node.js, “hot reload” thường thực chất là process restart nhanh. Với frontend, HMR có thể cập nhật module qua WebSocket mà không reload toàn bộ trang.
Luồng thay đổi file
Mermaid
Loading diagram…
Nếu một mắt xích hỏng, reload không xảy ra. Vì vậy cần debug theo từng tầng thay vì đổi ngẫu nhiên nhiều cấu hình.
Cấu trúc dự án mẫu
package.json cho Node.js 22:
Server phải listen trên 0.0.0.0
Bình luận
:
Nếu bind 127.0.0.1 bên trong Container, host có thể không truy cập được dù port đã publish.
Dockerfile có development stage
Development và production dùng contract khác nhau:
Development: source thay đổi, watcher chạy, debug port có thể mở.
Production: Image immutable, không mount source, không chạy dev server.
.dockerignore tối thiểu
Không gửi node_modules của host vào build context. Native dependency của macOS/Windows có thể không chạy trong Linux Container.
Cách 1: Bind mount — đơn giản và phổ biến
compose.dev.yaml:
Chạy:
Khi sửa src/server.js:
Bind mount cập nhật /app/src/server.js.
node --watch nhận event.
Node restart process.
Log mới xuất hiện trong terminal.
Vì sao cần volume riêng cho node_modules?
Bind mount .:/app che toàn bộ /app đã có trong Image. Nếu không giữ /app/node_modules ở volume riêng:
Dependency cài trong Image bị che.
Container có thể thấy node_modules của host.
Native module có thể sai OS/architecture.
giữ dependency Linux trong Docker volume.
Cập nhật dependency mà không phá dữ liệu database
Khi Container đang chạy:
Lệnh cập nhật dependency trong volume và lockfile trên bind mount. Kiểm tra quyền sở hữu file sau đó, đặc biệt khi Container chạy bằng root.
Cài sạch:
Nếu cần xóa volume dependency, hãy xác định chính xác tên volume:
Không dùng bừa:
nếu stack còn volume PostgreSQL/MongoDB, vì -v có thể xóa cả dữ liệu local.
Cách 2: Docker Compose Watch
Compose Watch đồng bộ file chọn lọc thay vì bind mount toàn repository. Nó thường hữu ích khi filesystem sharing trên macOS/Windows chậm hoặc monorepo quá lớn.
compose.yaml:
Khởi động bằng một trong các cách được Compose version hỗ trợ:
hoặc:
Kiểm tra compatibility:
Logic:
Source đổi → sync vào Container → watcher của Node restart process.
package.json/lockfile đổi → rebuild Image và recreate service.
Không cấu hình bind mount toàn project cùng lúc với Watch cho cùng path; hai cơ chế có thể ghi đè hoặc gây hành vi khó đoán.
Chọn Bind Mount hay Compose Watch?
Bắt đầu bằng bind mount. Chuyển sang Compose Watch khi cần sync chọn lọc hoặc hiệu năng filesystem tốt hơn.
Dùng Nodemon
Cài:
package.json:
nodemon.json:
Nếu file event không truyền ổn định qua Docker Desktop:
Polling/legacy watch tăng CPU. Chỉ bật khi native events không hoạt động.
TypeScript với tsx watch
Compose vẫn dùng command: npm run dev; chỉ watcher bên trong thay đổi.
Tránh vừa chạy TypeScript compiler watch vừa để một watcher khác restart trên mọi file generated nếu output nằm trong path đang theo dõi — điều này có thể tạo vòng lặp restart.
Vite / React HMR trong Container
Dev server phải listen trên mọi interface:
Compose:
HMR dùng WebSocket. Nếu trang load nhưng HMR không hoạt động, kiểm tra:
Dev server bind 0.0.0.0.
Port HMR được publish đúng.
Reverse proxy hỗ trợ WebSocket upgrade.
Browser truy cập đúng host/port.
Firewall/VPN không chặn WebSocket.
Next.js development trong Docker
Script:
Compose:
Giữ .next trong volume có thể cải thiện I/O, nhưng cache cũ đôi khi gây lỗi. Chỉ xóa volume cache mục tiêu khi cần, không xóa volume database.
Polling: phương án fallback
Trên một số filesystem hoặc VM boundary, watcher không nhận event. Framework cung cấp polling:
Chỉ bật biến tương ứng với tool đang dùng. Polling trên monorepo lớn có thể:
Tăng CPU.
Tăng disk I/O.
Làm laptop nóng.
Gây restart liên tục nếu watch cả output/cache.
Giảm phạm vi watch trước khi tăng polling frequency.
Linux và giới hạn inotify
Ứng dụng lớn có thể gặp lỗi gần giống:
Trước khi tăng giới hạn:
Kiểm tra có watch nhầm node_modules, .git, build output hay không.
Giảm path và extension được theo dõi.
Kiểm tra process watcher bị nhân đôi.
Chỉ điều chỉnh host sysctl theo policy của máy/team.
Không đặt một giá trị cực lớn mù quáng vì watcher cũng tiêu thụ tài nguyên kernel.
Quyền file và UID/GID
Bind mount giữ permission của host. Container chạy root có thể tạo file root-owned trên máy developer.
Các lựa chọn:
Chạy bằng user không phải root trong development Image.
Map UID/GID theo môi trường team nếu thực sự cần.
Không chạy npm install bằng root trên bind-mounted project.
Chỉ mount path cần chỉnh sửa.
Ví dụ dùng user có sẵn trong official Node Image:
Nếu mount host directory sau đó, quyền thực tế vẫn phụ thuộc host. Test trên Linux, macOS và Windows thay vì giả định một cấu hình UID phù hợp mọi nơi.
Tách development khỏi production
Base compose.yaml:
Dev override compose.dev.yaml:
Chạy:
Production không nên kế thừa bind mount, dev dependency hoặc polling env.
Vì sao dùng init: true?
Dev watcher thường spawn child process. init: true thêm init process nhỏ để:
Forward signal tốt hơn.
Reap zombie process.
Giúp Ctrl+C và shutdown ổn định.
Watcher vẫn phải xử lý SIGTERM đúng cách và đóng database connection/server gracefully.
Không lạm dụng restart policy trong development
có thể che crash loop và làm log khó đọc. Trong môi trường dev, để watcher restart khi source đổi, nhưng để lỗi cấu hình nghiêm trọng dừng service rõ ràng thường dễ debug hơn.
Bảo mật môi trường development
Bind port vào 127.0.0.1 nếu không cần LAN access.
Không mount Docker socket vào app chỉ để hot reload.
Không mount home directory hoặc SSH keys.
Đưa .env vào .gitignore và .dockerignore.
Không dùng production credential trong local Compose.
Chỉ mount source cần thiết, ưu tiên read-only cho config không cần sửa.
Không đưa dev server trực tiếp ra Internet.
Hot reload tăng quyền đọc/ghi của Container lên source host; chỉ chạy Image và dependency đáng tin cậy.
Debug khi sửa file nhưng app không reload
1. File có vào Container không?
Nếu content không đổi, lỗi nằm ở bind mount/Compose Watch.
2. Watcher có chạy không?
Kiểm tra command thực tế là npm run dev, không phải npm start.
3. Watcher có theo dõi đúng path không?
Nếu source ở /app/src, cấu hình watch src, không phải thư mục host không tồn tại trong Container.
4. Có bị bind mount che file không?
Kiểm tra mounts và target path.
5. Process restart nhưng browser không cập nhật?
Backend restart không tự reload browser. Frontend cần live-reload/HMR client hoặc browser extension/workflow riêng.
6. HMR WebSocket có lỗi không?
Mở browser DevTools → Network → WS và kiểm tra handshake, host, port, proxy headers.
7. CPU tăng cao?
Giảm polling, loại node_modules, .git, .next, dist, coverage và logs khỏi watch path.
Anti-pattern phổ biến
Rebuild Image sau mỗi lần sửa source
không nên là vòng lặp cho từng file. Dùng sync/mount cho source; chỉ rebuild khi dependency hoặc Dockerfile đổi.
Mount node_modules từ host
Có thể gây binary mismatch và permission lỗi.
Watch cả repository không loại cache/output
Dễ tạo CPU cao và restart loop.
Bật polling ngay từ đầu
Che root cause và tốn tài nguyên.
Dùng cùng Compose config cho dev và production
Dễ vô tình deploy bind mount, dev server hoặc debug port.
Xóa toàn bộ volume để sửa cache
Có thể xóa dữ liệu database không thể phục hồi.
Checklist developer experience
Source host xuất hiện trong Container qua bind mount hoặc Compose Watch.
Chỉ dùng một cơ chế sync cho cùng path.
Dev command chạy node --watch, Nodemon, tsx watch, Vite hoặc Next dev.
App/dev server bind 0.0.0.0.
node_modules nằm trong Image hoặc Docker volume, không lấy từ host.
Lockfile change trigger install/rebuild rõ ràng.
Watch path loại node_modules, .git, cache và output.
Docker Compose khi nào không còn đủ? So sánh Kubernetes và Docker Swarm về multi-host, self-healing, scaling, rolling update, networking, secrets và lộ trình migration production.
Hướng dẫn Docker Compose cho Node.js với hai cấu hình docker-compose.yml hoàn chỉnh: PostgreSQL và MongoDB, kèm healthcheck, service discovery, volume, bảo mật và debug.