Deploy nhiều app NodeJS trên cùng một VPS bằng Docker: an toàn, gọn, dễ vận hành
Chạy nhiều app NodeJS trên một VPS nghe đơn giản: cài Node, clone repo, pm2 start, xong. Nhưng rất nhanh sẽ gặp hỗn loạn: port đụng nhau, log khó đọc, SSL rối, update dễ downtime, một app lỗi kéo theo cả máy nghẹt tài nguyên. Quy mô nhỏ vẫn chịu được. Quy mô vừa → bắt đầu đau.
Giải pháp thực dụng nhất cho đa số team nhỏ, startup, freelancer: Docker + reverse proxy + tách mạng + quy ước deploy rõ ràng. Mục tiêu không phải “enterprise”, mà là: an toàn hơn, deploy nhanh hơn, rollback dễ hơn, quản lý đỡ mệt hơn.
Bài này đi thẳng vào mô hình triển khai nhiều app NodeJS trên cùng một VPS theo hướng ổn định, bảo mật cơ bản tốt, dễ bảo trì.
Vì sao nên dùng Docker thay vì chạy thẳng Node trên VPS?
Chạy thẳng nhiều app NodeJS trên OS host thường dẫn tới:
– Xung đột môi trường: app A cần Node 18, app B cần Node 20.
– Port lộn xộn: app nào cũng muốn 3000, 4000.
– Deploy khó chuẩn hóa: mỗi app một script.
– Rollback kém: update lỗi → sửa tay.
– Khó cô lập: app ăn RAM/CPU quá mức → ảnh hưởng app khác.
Docker giải quyết khá gọn:
– Mỗi app = một container → cô lập runtime.
– Image bất biến → build 1 lần, chạy ổn định.
– Port nội bộ riêng → reverse proxy route domain đúng app.
– Dễ CI/CD → docker compose pull && up -d.
– Rollback nhanh → quay lại image cũ.
Docker không tự làm hệ thống “an toàn tuyệt đối”. Nhưng nó tạo nền tốt để bạn kiểm soát tốt hơn.
Kiến trúc nên dùng: 1 reverse proxy, nhiều app container
Mô hình khuyến nghị:
– 1 VPS – 1 reverse proxy container: Nginx, Traefik, hoặc Caddy – N app NodeJS, mỗi app một container – Database: – tốt nhất: tách VPS/managed DB – chấp nhận được: DB container riêng, không public port – Docker network riêng – SSL terminate tại reverse proxy
Luồng chuẩn:
– User vào app1.example.com
– Reverse proxy nhận request
– Proxy route tới container app1
– App xử lý
– Response trả về qua proxy
Lợi ích lớn:
– Chỉ mở public 80/443 – App NodeJS không cần public port ra Internet – SSL tập trung một chỗ – Domain/subdomain mapping rõ ràng
Cấu trúc thư mục nên gọn, chuẩn ngay từ đầu
Trên VPS, nên thống nhất kiểu tổ chức:
/opt/apps/
reverse-proxy/
app-a/
app-b/
app-c/Mỗi app nên có:
– Dockerfile
– docker-compose.yml hoặc nằm trong compose tổng
– .env
– thư mục volume nếu cần log/upload
Ví dụ:
/opt/apps/app-a/
Dockerfile
docker-compose.yml
.envĐừng để mỗi app một kiểu. Kỷ luật thư mục → vận hành nhàn hơn rất nhiều.
Dockerfile cho NodeJS: nhỏ, sạch, production-first
Một lỗi phổ biến: dùng cùng image dev để chạy production. Kết quả: image nặng, nhiều package thừa, build chậm, bề mặt tấn công rộng hơn.
Nên dùng multi-stage build:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]
Điểm quan trọng:
– node:alpine → image nhẹ
– npm ci → reproducible
– --omit=dev → production gọn hơn
– USER node → không chạy bằng root
– Chỉ copy thứ cần thiết
Thêm .dockerignore:
node_modules
npm-debug.log
.git
.env
dist.env không nên bake vào image.
Docker Compose: cách đơn giản nhất để quản lý nhiều app
Với 1 VPS, docker compose đủ tốt cho đa số nhu cầu. Chưa cần Kubernetes.
Ví dụ app:
services:
app-a:
build: .
container_name: app-a
restart: unless-stopped
env_file:
- .env
networks:
- web
expose:
- "3000"
mem_limit: 512m
cpus: 0.50
networks:
web:
external: true
Ý chính:
– restart: unless-stopped → tự khởi động lại
– expose thay vì ports → chỉ mở nội bộ Docker
– giới hạn RAM/CPU → app lỗi không kéo sập VPS
– dùng external network chung cho proxy + app
Tạo network 1 lần:
docker network create webReverse proxy: mảnh ghép quan trọng nhất
Nếu muốn dễ nhất, Caddy rất đáng dùng vì tự cấp SSL. Nếu muốn phổ biến hơn, chọn Nginx + Certbot. Nếu muốn tự động routing mạnh, chọn Traefik.
Với người mới, Caddy thường ít đau đầu nhất.
Ví dụ Caddyfile:
app1.example.com {
reverse_proxy app-a:3000
}
app2.example.com {
reverse_proxy app-b:3000
}
Compose cho Caddy:
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- web
volumes:
caddy_data:
caddy_config:
networks:
web:
external: true
Kết quả:
– SSL tự cấp/tự renew – app không cần publish port – thêm app mới → thêm 1 block domain
Bảo mật: tối thiểu phải làm đúng
Đây là phần nhiều người bỏ qua nhất.
1. Chỉ mở port cần thiết
Firewall VPS:
– mở 22, 80, 443
– đóng mọi port khác
– nếu được, giới hạn SSH theo IP
Ubuntu dùng ufw:
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw enable2. Tắt SSH password, dùng key
– dùng SSH key – tắt login bằng password – nếu có thể, tắt root login
SSH hở password → bot quét liên tục.
3. Không public database
Nếu chạy PostgreSQL/MySQL trên cùng VPS:
– không map 5432:5432 hoặc 3306:3306 ra ngoài
– chỉ cho app truy cập qua Docker network
DB public port → rủi ro tăng mạnh.
4. Secrets không hard-code
Không commit:
– .env
– API key
– DB password
– JWT secret
Tối thiểu:
– lưu trong .env trên VPS
– phân quyền file chặt
– tách env theo app
5. Chạy container non-root
Trong app container:
– dùng USER node
– tránh mount quyền ghi tràn lan
– nếu không cần, mount filesystem read-only
6. Giới hạn tài nguyên
Một app memory leak → cả VPS swap, treo. Fix:
– mem_limit
– cpus
– log rotation
Logging, monitoring, backup: đừng đợi có sự cố mới làm
Nhiều app trên 1 VPS mà không có quan sát → mù.
Logging
Tối thiểu:
– app log ra stdout/stderr
– xem bằng:
docker logs app-a --tail 100 -fBật log rotation ở Docker daemon hoặc dùng driver phù hợp. Nếu không, log phình đầy disk.
Monitoring
Ít nhất theo dõi:
– CPU – RAM – disk – restart count container – uptime
Tool nhẹ:
– htop
– docker stats
– Uptime Kuma
– Netdata
Backup
Cần backup:
– database
– file upload
– file cấu hình như Caddyfile, compose, .env
Nguyên tắc:
– backup tự động hàng ngày – lưu ngoài VPS – test restore định kỳ
Backup không test restore → gần như chưa có backup.
Quy trình deploy nên đơn giản, lặp lại được
Một flow thực dụng:
1. Build image từ CI hoặc ngay trên VPS 2. Pull code/image mới 3. Chạy:
docker compose up -d --build4. Check health/log 5. Nếu lỗi → rollback image cũ
Tốt hơn nữa:
– gắn tag image theo version/git commit
– không dùng mãi latest
– thêm healthcheck để proxy không route vào app chưa sẵn sàng
Ví dụ:
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3App có endpoint /health → deploy an toàn hơn nhiều.
Khi nào mô hình này đủ, khi nào nên nâng cấp?
Mô hình 1 VPS + Docker phù hợp nếu:
– 2-10 app nhỏ/vừa – traffic chưa quá lớn – team ít người – cần tối ưu chi phí – muốn deploy nhanh, quản lý đơn giản
Nên nghĩ tới nhiều VPS, orchestrator, hoặc managed platform khi:
– cần high availability – cần auto-scaling – traffic tăng mạnh – yêu cầu zero-downtime chặt – nhiều người cùng vận hành
Đừng “Kubernetes quá sớm”. Nhiều trường hợp chỉ làm hệ thống phức tạp hơn.
Kết luận
Deploy nhiều app NodeJS trên cùng một VPS không khó. Khó ở chỗ giữ nó gọn, an toàn, dễ sống cùng lâu dài. Công thức thực tế nhất thường là:
– Docker để cô lập app – Docker Compose để quản lý – Reverse proxy để route domain + SSL – Firewall + SSH key + DB không public để giảm rủi ro – Giới hạn tài nguyên + backup + monitoring để tránh sự cố lan rộng
Nếu làm đúng từ đầu, bạn sẽ có một hệ thống:
– deploy nhanh – thêm app dễ – rollback gọn – ít port hở – dễ debug hơn nhiều so với chạy tay từng app
Tóm ngắn: 1 VPS vẫn đủ mạnh và chuyên nghiệp, miễn là bạn triển khai có quy ước, có cô lập, có bảo mật tối thiểu. Không cần quá nhiều công cụ. Cần đúng công cụ, đúng cấu trúc, đúng kỷ luật vận hành.
Bình luận (0)
Chưa có bình luận. Hãy là người đầu tiên!