Mẫu Dockerfile và Docker Compose chuẩn để deploy NodeJS trên VPS

09/05/2026 · P T P · Chung

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ẩnmô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
npm-debug.log
.git
.gitignore
Dockerfile
docker-compose.yml
.env
dist
coverage
.vscode

Ý nghĩa

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ý.

Ví dụ app NodeJS + PostgreSQL:

version: "3.9"

services: app: build: context: . dockerfile: Dockerfile container_name: node_app restart: unless-stopped env_file: - .env ports: - "3000:3000" depends_on: - postgres networks: - app_net

postgres: image: postgres:16-alpine container_name: postgres_db restart: unless-stopped environment: POSTGRES_DB: appdb POSTGRES_USER: appuser POSTGRES_PASSWORD: strongpassword volumes: - postgres_data:/var/lib/postgresql/data networks: - app_net

volumes: postgres_data:

networks: app_net: driver: bridge

Điểm quan trọng

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

Ví dụ:

version: "3.9"

services: app: build: . restart: unless-stopped env_file: - .env expose: - "3000" networks: - backend

nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - app networks: - backend

networks: backend: driver: bridge

Vì sao expose tốt hơn ports?

ports → publish ra ngoài VPS
expose → chỉ mở cho container khác trong cùng network

App nội bộ → bề mặt tấn công thấp hơn.


Biến môi trường: chuẩn cách dùng để tránh rò rỉ

Nên giữ .env trên VPS, không commit Git.

Ví dụ:

NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://appuser:strongpassword@postgres:5432/appdb
JWT_SECRET=change_me

Best practice

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.

Thêm healthcheck

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

App nên có endpoint /health.

Giới hạn log

services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Log không giới hạn → đầy disk VPS.

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

services:
  app:
    mem_limit: 512m
    cpus: 0.5

App leak RAM → không kéo sập cả máy.


Quy trình deploy thực tế trên VPS

Một flow đơn giản, dễ áp dụng:

1. Chuẩn bị VPS

– Cài Docker
– Cài Docker Compose plugin
– Mở firewall cho 80, 443, 22

2. Upload code

git clone <repo>
cd <project>

3. Tạo file môi trường

cp .env.example .env
nano .env

4. Build và chạy

docker compose up -d --build

5. Kiểm tra

docker compose ps
docker compose logs -f app

6. Update phiên bản mới

git pull
docker compose up -d --build

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:

Sailocalhost:5432
Đúngpostgres: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
.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.

#chuan #compose #deploy #docker #dockerfile
Chia sẻ:
← Trước
Giảm 70% Chi Phí Server Khi Deploy NodeJS Docker Trên VPS Startup
Sau →
Chạy Next.js trên VPS từ A-Z: Nhanh, Ổn Định, Ai Cũng Làm Được

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!