Triển khai Node.js với Docker + Nginx — Case Study
Bạn đã viết xong API bằng Node.js, test local ngon lành. Nhưng khi đưa lên server thật, bạn gặp hàng loạt câu hỏi:
- Làm sao chạy app ổn định, tự restart khi crash?
- Làm sao để client không truy cập thẳng vào port 3000?
- Làm sao xử lý SSL, rate limit, static files?
Câu trả lời: Docker + Nginx reverse proxy. Trong bài viết này, tôi sẽ hướng dẫn bạn triển khai một ứng dụng Node.js (Express) từ đầu đến cuối, sử dụng Docker để đóng gói và Nginx làm reverse proxy phía trước.
Kiến trúc tổng quan
| |
Nginx đứng trước nhận request từ client, sau đó chuyển tiếp (proxy) đến Node.js chạy bên trong. Client không bao giờ truy cập trực tiếp vào Node.js.
Tại sao cần Nginx phía trước?
| Lý do | Giải thích |
|---|---|
| Reverse proxy | Ẩn port thật của app, client chỉ thấy port 80/443 |
| Static files | Nginx phục vụ file tĩnh (CSS, JS, ảnh) nhanh hơn Node.js rất nhiều |
| SSL termination | Nginx xử lý HTTPS, Node.js chỉ nhận HTTP thuần |
| Rate limiting | Giới hạn request ở tầng Nginx, bảo vệ Node.js khỏi quá tải |
| Load balancing | Khi scale nhiều instance Node.js, Nginx phân phối request |
Bước 1: Tạo ứng dụng Node.js (Express)
Cấu trúc project
| |
File package.json
| |
File src/server.js
| |
Lưu ý:
app.listen(PORT, "0.0.0.0")— phải bind0.0.0.0để container nhận được request từ bên ngoài. Nếu bind127.0.0.1(localhost), Nginx sẽ không thể kết nối đến app.
Bước 2: Viết Dockerfile cho Node.js
File .dockerignore
| |
Theo tài liệu chính thức của Docker, Dockerfile cho Node.js nên dùng multi-stage build, tạo non-root user và sử dụng cache mount để tối ưu.
File Dockerfile
| |
Giải thích:
| Kỹ thuật | Mục đích |
|---|---|
node:18-alpine | Base image nhỏ (~170 MB thay vì ~900 MB của node:18) |
| Multi-stage build | Stage deps cài dependencies, stage production chỉ copy kết quả |
npm ci | Cài đặt chính xác theo package-lock.json, đảm bảo reproducible builds |
--mount=type=cache | Cache npm packages giữa các lần build |
| Non-root user | Không chạy app bằng root, tăng bảo mật |
COPY src ./src | Chỉ copy source code cần thiết, không copy toàn bộ project |
Bước 3: Cấu hình Nginx làm Reverse Proxy
File nginx/default.conf
Theo tài liệu chính thức của Nginx, directive proxy_pass được dùng để chuyển tiếp request đến server khác. Các header proxy_set_header cần được thiết lập để server backend nhận đúng thông tin client.
| |
Giải thích các directive quan trọng:
| Directive | Ý nghĩa |
|---|---|
upstream nodejs_app | Định nghĩa nhóm backend server. Khi scale nhiều instance, thêm server vào đây |
proxy_pass http://nodejs_app | Chuyển tiếp request đến upstream đã định nghĩa |
proxy_set_header Host $host | Giữ nguyên hostname gốc từ client (mặc định Nginx đổi thành $proxy_host) |
proxy_set_header X-Real-IP | Gửi IP thật của client đến Node.js (không phải IP của Nginx) |
proxy_set_header X-Forwarded-For | Chuỗi IP qua các proxy, giúp Node.js biết client thật |
proxy_set_header X-Forwarded-Proto | Cho Node.js biết client dùng HTTP hay HTTPS |
proxy_http_version 1.1 | Bắt buộc để hỗ trợ WebSocket và keep-alive |
Bước 4: Docker Compose — Kết nối tất cả
Theo Docker Compose production guide, khi deploy production nên: bỏ volume bind cho code, set restart policy, và tách file compose cho từng môi trường.
File docker-compose.yml
| |
Các điểm quan trọng:
| Cấu hình | Giải thích |
|---|---|
expose: "3000" | Chỉ mở port nội bộ giữa các container, không expose ra host |
ports: "80:80" | Chỉ Nginx được expose ra ngoài |
depends_on: condition: service_healthy | Nginx chỉ start sau khi Node.js healthy |
restart: unless-stopped | Tự restart khi crash, trừ khi bạn chủ động stop |
:ro (read-only) | Mount config Nginx ở chế độ chỉ đọc |
networks: app-network | Cả 2 container cùng network, Nginx gọi app:3000 qua tên service |
Bước 5: Build và chạy
| |
Kiểm tra hoạt động:
| |
Kết quả mong đợi:
| |
Bước 6: Deploy lại khi thay đổi code
Theo Docker Compose docs, khi cập nhật code, bạn chỉ cần rebuild service cần thiết:
| |
Flag --no-deps đảm bảo chỉ restart service app, không restart Nginx. Client không bị gián đoạn.
Mở rộng: Scale nhiều instance Node.js
Khi traffic tăng, bạn có thể chạy nhiều instance Node.js và để Nginx load balance:
| |
Cập nhật nginx/default.conf để Nginx biết có nhiều instance:
| |
Khi dùng Docker Compose với --scale, Docker DNS tự động resolve app thành tất cả IP của các container app. Nginx sẽ round-robin request đến từng instance.
Ghi chú triển khai
- Khi áp dụng vào dự án của bạn, hãy lưu ý:
- Luôn dùng
exposethay vìportscho service backend — chỉ Nginx mới cần expose ra ngoài - Bind
0.0.0.0trong Node.js, không bind127.0.0.1 - Dùng
npm cithaynpm installđể đảm bảo reproducible builds - Tạo non-root user trong Dockerfile cho bảo mật
- Luôn dùng
- Best practices:
- Dùng
docker compose up --no-deps -d appkhi chỉ cần redeploy app - Pin version cho base image (
node:18-alpine,nginx:1.27-alpine) thay vìlatest - Tách
docker-compose.prod.ymlriêng cho production config
- Dùng
- Troubleshooting:
- Nginx báo
502 Bad Gateway? → Node.js chưa start xong hoặc bind sai address. Kiểm tradocker compose logs app - Không kết nối được giữa Nginx và Node.js? → Đảm bảo cả 2 cùng network và dùng đúng tên service trong
proxy_pass - Request bị timeout? → Kiểm tra
client_max_body_sizevàproxy_read_timeouttrong Nginx config
- Nginx báo
🎯 Lời kết
Tóm tắt lại flow triển khai:
- Viết app Node.js (Express) với health check endpoint
- Dockerfile multi-stage build + non-root user
- Nginx config reverse proxy với
proxy_passvà các header cần thiết - Docker Compose kết nối app + Nginx, chỉ expose port 80
- Deploy bằng
docker compose up --build -d - Update bằng
docker compose build app && docker compose up --no-deps -d app
Đây là kiến trúc cơ bản nhất để đưa một ứng dụng Node.js lên production. Từ đây bạn có thể mở rộng thêm SSL (Let’s Encrypt), database, monitoring tuỳ nhu cầu. 🚀
Nếu bạn thấy bài viết này hữu ích, hãy nhớ ⭐ star repo hoặc 📱 chia sẻ với bạn bè nhé! 😊
Tài liệu tham khảo
- Docker Official - Containerize Node.js — Hướng dẫn chính thức Docker cho Node.js
- Docker Compose in Production — Hướng dẫn deploy production với Compose
- Nginx Reverse Proxy — Tài liệu chính thức cấu hình reverse proxy
- Nginx Beginner’s Guide — Hướng dẫn cơ bản từ nginx.org
