Deploy NodeJS trên VPS: vì sao Dockerfile và Docker Compose “chuẩn” đáng giá từng phút cấu hình
Deploy NodeJS lên VPS tưởng đơn giản: git pull, npm install, pm2 restart. Chạy được. Nhưng vài tuần sau thường phát sinh: máy build lỗi vì khác version Node, app restart không tự lên, log khó đọc, rollback chậm, môi trường dev/prod lệch nhau, Nginx/DB/cache đụng port.
Dockerfile + Docker Compose chuẩn → môi trường nhất quán, dễ deploy, dễ scale, dễ debug.
Bài này tập trung vào mẫu cấu hình thực dụng cho production trên VPS, đủ “sạch” để dùng lâu dài, đủ linh hoạt để mở rộng. Mục tiêu: build nhỏ, chạy ổn định, bảo mật cơ bản, dễ maintain.
Tư duy đúng trước khi viết Dockerfile
Nhiều dự án NodeJS gặp 3 lỗi phổ biến:
– Copy toàn bộ source quá sớm → cache build kém, build chậm
– Chạy bằng root → rủi ro bảo mật
– Dùng image nặng → tốn disk, tốn băng thông, startup chậm
Một Dockerfile tốt nên có:
– Base image rõ version
– Tận dụng layer cache
– Multi-stage build nếu có TypeScript/NestJS/Next build server
– Chỉ mang dependency cần thiết sang production
– User non-root
– Healthcheck/log/env rõ ràng
Mẫu Dockerfile chuẩn cho NodeJS production
Giả sử app NodeJS dạng API, có bước build như TypeScript dist/.
# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
WORKDIR /app
FROM base AS deps
COPY package*.json ./
RUN npm ci
FROM deps AS build
COPY . .
RUN npm run build
FROM node:20-alpine AS prod
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]
Vì sao mẫu này tốt?
– node:20-alpine → nhẹ, đủ phổ biến cho prod
– COPY package*.json trước → đổi source không làm invalid cache cài package
– npm ci → cài đúng theo lockfile, ổn định hơn npm install
– Tách stage build/prod → image cuối nhỏ hơn
– --omit=dev → prod không mang devDependencies
– USER node → không chạy root
Khi nào cần chỉnh?
– App thuần JS, không build → bỏ stage build
– Dùng pnpm/yarn → thay lệnh cài package
– Dùng native module như sharp, bcrypt, prisma → có thể cần image Debian thay Alpine nếu gặp lỗi binary/libc
Ví dụ bản đơn giản cho app không build:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Đừng quên .dockerignore
Thiếu file này → image phình to, leak file nhạy cảm, build chậm.
– node_modules → tránh copy thư viện local vào container
– .git → giảm kích thước build context
– .env → tránh nhét secret vào image
– dist → nếu build trong Docker thì không cần copy từ máy host
Mẫu Docker Compose chuẩn để deploy trên VPS
Docker Compose giúp gom app + network + volume + restart policy trong một file dễ quản lý.
– restart: unless-stopped → reboot VPS xong container tự lên
– env_file → tách config khỏi code
– depends_on → đảm bảo thứ tự start cơ bản
Lưu ý: nó không chờ DB ready hoàn toàn
– volumes → dữ liệu DB không mất khi recreate container
– networks → service giao tiếp nội bộ sạch hơn
Mẫu Compose tốt hơn cho production
Production thật thường không expose app trực tiếp. Thay vào đó:
– Nginx/Caddy → reverse proxy
– Chỉ mở 80/443
– App chạy internal network
– DB không public port
– Không hardcode secret trong docker-compose.yml
– Không bake .env vào image
– Tách .env.production nếu có nhiều môi trường
– Rotate secret định kỳ nếu app quan trọng
Nếu cần bảo mật cao hơn → dùng Docker secrets, Vault, SSM. Nhưng với VPS nhỏ, .env + quyền file chuẩn đã đủ cho nhiều case.
Healthcheck, logging, tài nguyên: phần hay bị bỏ qua
Docker chạy được chưa đủ. Cần biết app có “sống khỏe” không.
Flow này tối ưu cho đa số app nhỏ-vừa, ít phụ thuộc CI/CD phức tạp.
Những lỗi phổ biến khi deploy NodeJS bằng Docker
1. Container chạy rồi thoát ngay
Nguyên nhân thường gặp:
– Sai CMD
– App crash khi boot
– Thiếu biến môi trường
– Build không tạo ra dist/
Kiểm tra:
docker compose logs -f app
2. App không kết nối được DB
Thường do dùng localhost.
Trong Docker network:
– Sai → localhost:5432
– Đúng → postgres:5432
Tên host phải là tên service.
3. Permission lỗi khi ghi file
Nguyên nhân:
– Chạy bằng USER node
– Thư mục mount từ host không đúng quyền
Fix:
– Set quyền thư mục host phù hợp
– Hoặc mount vào path app có thể ghi
4. Build rất chậm
Nguyên nhân:
– Thiếu .dockerignore
– COPY . . quá sớm
– Cache layer bị phá liên tục
Fix → giữ COPY package*.json trước, source sau.
Kết luận: “chuẩn” không phải phức tạp, mà là dễ vận hành
Một cấu hình Dockerfile và Docker Compose tốt cho NodeJS trên VPS nên đạt 4 mục tiêu:
– Nhẹ → image nhỏ, build nhanh
– Ổn định → lockfile, restart policy, volume
– An toàn hơn → non-root, app không expose trực tiếp
– Dễ bảo trì → env tách riêng, logs rõ, update đơn giản
Nếu bạn chỉ nhớ 5 điểm cốt lõi, hãy nhớ:
– Dùng npm ci
– Có .dockerignore
– Multi-stage build khi cần
– Không chạy app bằng root
– Dùng Compose để quản lý app + DB + network
Từ nền này, bạn có thể đi tiếp sang Nginx reverse proxy, SSL tự động với Caddy/Traefik, CI/CD bằng GitHub Actions, zero-downtime deploy. Nhưng ngay cả trước khi tối ưu nâng cao, chỉ cần áp dụng đúng mẫu cơ bản trong bài viết này, bạn đã có một quy trình deploy NodeJS trên VPS sạch hơn, bền hơn, ít lỗi hơn rất nhiều.