Deploy NodeJS bằng Docker trên VPS: Lỗi Hay Gặp, Cách Gỡ Nhanh

07/05/2026 · P T P · Chung

Deploy NodeJS bằng Docker trên VPS: những lỗi rất hay gặp, vì sao xảy ra, cách xử lý gọn mà đúng

Deploy NodeJS lên VPS bằng Docker nghe rất “êm”: build image, chạy container, map port, xong. Thực tế khác hẳn. App chạy tốt ở local nhưng lên VPS lại lỗi ngầm: container restart liên tục, app không truy cập được, build fail vì package native, memory tăng dần rồi chết, log im lặng đúng lúc cần nhất.

Điểm khó nằm ở chỗ: Docker, NodeJS, Linux VPS, reverse proxy, network, volume, quyền file, biến môi trường — mọi thứ liên kết nhau. Sai một mắt xích nhỏ → downtime, tốn giờ debug.

Bài này tập trung vào những lỗi phổ biến nhất khi deploy NodeJS bằng Docker trên VPS, kèm dấu hiệu nhận biết, nguyên nhân gốc, cách xử lý thực tế. Mục tiêu: giúp bạn không chỉ “chữa cháy”, mà còn dựng được quy trình deploy ổn định hơn.

1. App chạy trong container nhưng không truy cập được từ bên ngoài

Dấu hiệu

docker ps thấy container đang chạy
– Log app báo Server listening on 3000
– Truy cập IP hoặc domain trên VPS → timeout / connection refused

Nguyên nhân thường gặp

Bind sai host → app chỉ lắng nghe 127.0.0.1 trong container, không phải toàn bộ interface.

Ví dụ sai:

app.listen(3000, '127.0.0.1')

Trong container, cấu hình này khiến app chỉ mở cổng nội bộ cục bộ → Docker không forward đúng ra ngoài.

Cách xử lý

Cho app bind 0.0.0.0:

app.listen(process.env.PORT || 3000, '0.0.0.0')

Hoặc đơn giản:

app.listen(process.env.PORT || 3000)

Kèm kiểm tra mapping port:

docker run -p 3000:3000 my-node-app

Nếu có UFW/firewalld trên VPS, mở port tương ứng:

sudo ufw allow 3000

Nếu đi qua Nginx/Caddy → kiểm tra reverse proxy đang trỏ đúng localhost:3000 hay container network tương ứng.

2. Build Docker image thất bại vì node_modules, package native, khác môi trường

Dấu hiệu

– Build fail ở bước npm install hoặc npm ci
– Lỗi kiểu:
node-gyp rebuild failed
python not found
g++ not found
Unsupported platform
ELFCLASS64 / binary incompatibility

Nguyên nhân

NodeJS thường dùng package native như bcrypt, sharp, canvas, sqlite3, puppeteer. Chúng phụ thuộc OS, libc, compiler, system library. Local build trên macOS/Windows rồi copy node_modules vào image Linux → rất dễ vỡ.

Cách xử lý

Không copy node_modules từ local vào container. Dùng .dockerignore:

node_modules
npm-debug.log
.git
.env

Dockerfile nên cài dependency ngay trong image:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./ RUN npm ci --only=production

COPY . .

EXPOSE 3000 CMD ["node", "server.js"]

Nếu package native cần build tool → thêm dependency hệ thống:

RUN apk add --no-cache python3 make g++

Nếu dùng Debian/Ubuntu base:

RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*

Lưu ý: alpine nhẹ nhưng nhiều package native lỗi hơn vì dùng musl thay glibc. Nếu gặp lỗi khó chịu với sharp, canvas, puppeteer → thử chuyển sang node:20-slim.

3. Container chạy rồi tự thoát ngay

Dấu hiệu

docker ps không thấy container sống lâu
docker ps -a thấy status Exited
– Log rất ngắn hoặc không có gì rõ ràng

Nguyên nhân

Container sống theo process chính. Process chính kết thúc → container dừng. Hay gặp khi:

– Sai CMD
– App crash ngay lúc start
– Script start chỉ chạy lệnh ngắn rồi thoát
– Thiếu biến môi trường bắt buộc

Cách xử lý

Xem log trước:

docker logs <container_id>

Kiểm tra Dockerfile:

CMD ["node", "server.js"]

Tránh kiểu shell script mơ hồ nếu không cần.

Nếu app phụ thuộc env, xác nhận .env hoặc biến runtime đã truyền đúng:

docker run --env-file .env -p 3000:3000 my-node-app

Dùng restart policy để tránh phải khởi động thủ công sau lỗi tạm thời:

docker run -d --restart unless-stopped -p 3000:3000 my-node-app

Nhưng nhớ: restart policy chỉ che triệu chứng. Gốc vẫn phải xem log.

4. Dùng sai biến môi trường, config production bị lệch

Dấu hiệu

– App connect DB fail trên VPS, local vẫn ổn
– JWT secret thiếu
– App chạy nhầm chế độ development
– CORS, cookie, URL callback lỗi

Nguyên nhân

Config local thường “dễ tính”. Lên VPS mới lộ ra:

– Quên mount .env
– Sai tên biến
– Hardcode localhost
– Dùng NODE_ENV=development ở production

Cách xử lý

Chuẩn hóa config theo nguyên tắc: fail fast. Thiếu env quan trọng → app dừng ngay, báo rõ.

Ví dụ:

const required = ['PORT', 'DATABASE_URL', 'JWT_SECRET']
for (const key of required) {
  if (!process.env[key]) {
    throw new Error(Missing env: ${key})
  }
}

Không hardcode URL nội bộ. Với Docker Compose, dùng tên service thay vì localhost cho kết nối giữa container:

– Sai: localhost:5432
– Đúng: postgres:5432

localhost trong container nghĩa là chính container đó, không phải VPS host, cũng không phải container DB khác.

5. Lỗi permission với volume, upload, log, cache

Dấu hiệu

– App không ghi được file upload
– Lỗi EACCES: permission denied
– Cache/session/file-based DB không hoạt động

Nguyên nhân

Mount volume từ host vào container nhưng user trong container không có quyền ghi. Rất hay gặp khi app chạy non-root hoặc image base có user riêng.

Cách xử lý

Kiểm tra quyền thư mục trên VPS:

ls -lah

Tạo thư mục đúng quyền:

mkdir -p uploads
chmod 775 uploads

Trong Dockerfile, chủ động set owner nếu cần:

RUN mkdir -p /app/uploads && chown -R node:node /app
USER node

Nếu dùng bind mount:

docker run -v $(pwd)/uploads:/app/uploads my-node-app

→ đảm bảo UID/GID tương thích. Đừng chmod 777 mọi thứ như một “giải pháp”.

6. App ngốn RAM, VPS swap mạnh, container bị kill

Dấu hiệu

– App chậm dần
– VPS đơ
– Container bị dừng đột ngột
docker logs có thể không rõ
dmesg thấy OOM kill

Nguyên nhân

NodeJS giữ memory cho V8; app có memory leak; log quá nhiều; xử lý file lớn; VPS quá ít RAM; chạy nhiều container chung máy.

Cách xử lý

Giới hạn tài nguyên container:

docker run -d --memory="512m" --cpus="1.0" my-node-app

Nếu app hợp lệ nhưng V8 cần cấu hình:

node --max-old-space-size=384 server.js

Theo dõi RAM:

docker stats
free -m

Tối ưu thêm:

– Không giữ object lớn quá lâu
– Stream file thay vì load hết vào memory
– Dùng PM2 trong container chỉ khi thật cần; đa số trường hợp 1 process/container là đủ
– Tách worker/background job khỏi web app nếu tải lớn

7. Log khó đọc, không có dữ liệu để debug

Dấu hiệu

– App lỗi nhưng không thấy gì trong docker logs
– Log chỉ có startup message
– Muốn truy log file trong container nhưng file mất sau redeploy

Nguyên nhân

Ghi log vào file nội bộ container → không bền. Docker chuẩn nhất là ghi ra stdout/stderr.

Cách xử lý

Trong NodeJS, log ra console:

console.log('App started')
console.error(err)

Xem log:

docker logs -f <container_id>

Nếu cần tập trung log:

– Dùng Nginx + Docker logs
– Hoặc đẩy sang Loki, ELK, Datadog, Better Stack

Thêm healthcheck để phát hiện app “sống giả chết thật”:

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 
  CMD wget -qO- http://localhost:3000/health || exit 1

8. Deploy xong downtime vì build trực tiếp trên VPS, rollback khó

Dấu hiệu

– Mỗi lần deploy phải SSH vào server, pull code, build tay
– Build lỗi giữa chừng → app cũ đã dừng
– Không có bản rollback rõ ràng

Nguyên nhân

Quy trình deploy thủ công, thiếu image versioning, thiếu reverse proxy ổn định, thiếu tách biệt build/run.

Cách xử lý

Thực tế nhất:

1. Build image ở CI hoặc local sạch
2. Tag version rõ ràng: my-app:1.4.2
3. Push registry
4. VPS chỉ pull + restart container
5. Giữ image cũ để rollback nhanh

Ví dụ:

docker pull myregistry/my-app:1.4.2
docker stop my-app
docker rm my-app
docker run -d --name my-app --restart unless-stopped -p 3000:3000 myregistry/my-app:1.4.2

Muốn tốt hơn nữa → dùng docker compose, reverse proxy, healthcheck, rolling strategy đơn giản.

9. Nginx/SSL/cookie hoạt động sai sau reverse proxy

Dấu hiệu

– HTTPS ngoài trình duyệt nhưng app nghĩ đang HTTP
– Cookie secure không set đúng
– Redirect loop
– IP client luôn là IP proxy

Nguyên nhân

NodeJS chưa trust proxy. Khi đi qua Nginx/Caddy/Traefik, app cần biết có proxy phía trước.

Cách xử lý

Với Express:

app.set('trust proxy', 1)

Kiểm tra cấu hình Nginx có forward header chuẩn:

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;

Điểm này cực quan trọng với auth, session, OAuth callback, secure cookie.

Kết luận: deploy ổn định không nằm ở “chạy được”, mà ở “chạy đúng lâu dài”

Deploy NodeJS bằng Docker trên VPS không khó ở lệnh docker run, mà khó ở các chi tiết môi trường thật: network, config, quyền file, reverse proxy, memory, log, quy trình cập nhật. Phần lớn sự cố production đến từ vài lỗi lặp đi lặp lại:

– bind sai host
– dùng sai env
– copy node_modules từ local
– không hiểu localhost trong container
– thiếu quyền volume
– không theo dõi RAM/log
– deploy thủ công, không version image

Nếu muốn giảm lỗi rõ rệt, hãy áp dụng bộ nguyên tắc ngắn gọn này: image sạch, config tường minh, log ra stdout, healthcheck rõ, reverse proxy chuẩn, resource limit hợp lý, deploy có version và rollback.

Làm được vậy → VPS nhỏ vẫn chạy ổn, debug nhanh hơn, downtime ít hơn nhiều. Nếu bạn đang deploy app thật cho khách hàng hoặc sản phẩm nội bộ, đó không còn là “tối ưu”, mà là mức tối thiểu nên có.

#bang #deploy #docker #nodejs #tren
Chia sẻ:
← Trước
Deploy NodeJS bằng Docker trên VPS không downtime dễ hơn bạn nghĩ

Bài viết tương tự

Bình luận

Chưa có bình luận. Hãy là người đầu tiên!