Deploy NodeJS API lên VPS bằng Docker + Nginx reverse proxy: gọn, sạch, dễ bảo trì
Deploy NodeJS API lên VPS nghe đơn giản: cài Node, git pull, chạy pm2, mở port. Làm được, nhưng dễ vỡ: lệch môi trường, restart lỗi, SSL rối, scale khó, rollback mệt. Cách ổn hơn: đóng gói app bằng Docker, để Nginx làm reverse proxy, nhận HTTPS, chuyển tiếp req vào container API.
Mô hình này phổ biến vì rõ trách nhiệm:
– Docker → chuẩn hóa môi trường chạy – NodeJS container → chỉ lo business logic – Nginx → public entrypoint, reverse proxy, SSL, gzip, rate limit cơ bản – VPS → host duy nhất, chi phí thấp, đủ cho nhiều side project/sản phẩm nhỏ
Bài này hướng dẫn triển khai thực chiến: từ chuẩn bị VPS, viết Dockerfile, docker-compose.yml, cấu hình Nginx, đến vận hành/log/debug.
Kiến trúc mục tiêu
Luồng req:
– Client → Nginx :80/:443 – Nginx → proxy → NodeJS API container :3000 – Docker network nội bộ → container nói chuyện với nhau – Chỉ Nginx expose ra Internet
Ưu điểm:
– Tách biệt tầng web/app – Ẩn port app nội bộ – Deploy/redeploy nhanh – Rollback dễ – Dễ thêm SSL bằng Let’s Encrypt
1. Chuẩn bị VPS
Tối thiểu:
– Ubuntu 22.04 hoặc 20.04 – RAM 1GB+ nếu API nhỏ – Domain trỏ A record về IP VPS – SSH access
Cập nhật hệ thống:
sudo apt update && sudo apt upgrade -yCài Docker + Compose plugin:
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg]
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" |
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Cho user hiện tại dùng Docker không cần sudo:
sudo usermod -aG docker $USER
newgrp dockerMở firewall:
sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable2. Cấu trúc project NodeJS phù hợp để container hóa
Ví dụ:
my-api/
├── src/
├── package.json
├── package-lock.json
├── .env
├── Dockerfile
├── .dockerignore
└── docker-compose.ymlĐiểm cần nhớ:
– App phải listen 0.0.0.0, không phải localhost
– Port nội bộ ví dụ 3000
– Secret/env không hard-code
Ví dụ Express:
const express = require('express');
const app = express();
app.get('/health', (req, res) => res.json({ ok: true }));
const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => {
console.log(API running on ${PORT});
});
3. Viết Dockerfile tối ưu cho NodeJS API
Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "src/index.js"]
Giải thích nhanh:
– node:20-alpine → image nhẹ
– npm ci → install ổn định theo lockfile
– --omit=dev → giảm dung lượng production
– EXPOSE 3000 → tài liệu hóa port app
Thêm .dockerignore để build sạch hơn:
node_modules
npm-debug.log
.git
.env
Dockerfile
docker-compose.ymlnode_modules local khác môi trường Linux → bỏ, để container tự cài.
4. Dùng Docker Compose để quản lý container
docker-compose.yml:
version: "3.9"
services:
api:
build: .
container_name: my-api
restart: always
env_file:
- .env
expose:
- "3000"
networks:
- appnet
nginx:
image: nginx:stable-alpine
container_name: my-nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./certbot/www:/var/www/certbot
- ./certbot/conf:/etc/letsencrypt
depends_on:
- api
networks:
- appnet
networks:
appnet:
Lưu ý:
– api dùng expose → chỉ mở cho network nội bộ
– nginx dùng ports → public ra ngoài
– restart: always → VPS reboot xong tự lên lại
5. Cấu hình Nginx reverse proxy
Tạo file nginx/default.conf:
server {
listen 80;
server_name api.yourdomain.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
proxy_pass http://api:3000;
proxy_http_version 1.1;
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;
}
}
Ý nghĩa:
– proxy_pass http://api:3000; → Nginx gọi container theo service name api
– Header X-Forwarded-* → app biết IP thật, protocol thật
– ACME path → phục vụ xác thực domain khi cấp SSL
Khởi động lần đầu:
docker compose up -d --buildTest:
curl http://api.yourdomain.com/healthNếu ra JSON → reverse proxy ok.
6. Cấp HTTPS bằng Let’s Encrypt
Có nhiều cách. Thực dụng nhất trên VPS nhỏ: dùng Certbot container hoặc cài certbot host. Ví dụ cài trên host:
sudo apt install -y certbotDừng Nginx container tạm nếu cần chiếm port 80. Hoặc giữ cấu hình ACME qua webroot. Tạo cert:
sudo certbot certonly --webroot -w ./certbot/www -d api.yourdomain.comSau khi có cert, cập nhật nginx/default.conf:
server {
listen 80;
server_name api.yourdomain.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name api.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
location / {
proxy_pass http://api:3000;
proxy_http_version 1.1;
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;
}
}
Reload:
docker compose restart nginxKết quả:
– HTTP → tự redirect HTTPS – SSL terminate tại Nginx – API container không cần xử lý chứng chỉ
7. Quy trình deploy thực tế
Một flow đơn giản:
1. SSH vào VPS 2. Pull code mới 3. Rebuild image 4. Restart container
Lệnh:
git pull origin main
docker compose up -d --buildNếu thay đổi chỉ ở code app → Compose rebuild service api. Nếu thay đổi config Nginx → container Nginx cũng reload lại.
Muốn dọn image cũ:
docker image prune -f8. Log, debug, health check
Xem log app:
docker compose logs -f apiXem log Nginx:
docker compose logs -f nginxKiểm tra container:
docker psVào shell trong container:
docker exec -it my-api shMột số lỗi phổ biến:
– 502 Bad Gateway → app chết, sai port, Nginx không gọi được api:3000
– Connection refused → app listen localhost thay vì 0.0.0.0
– SSL không lên → DNS chưa trỏ đúng, port 80/443 bị chặn
– Env không ăn → quên env_file, quên restart container
Nên có endpoint /health để monitor nhanh.
9. Best practices để chạy lâu dài
Vài khuyến nghị đáng tiền:
– Không expose port API ra ngoài nếu đã có Nginx
– Dùng .env riêng production
– Set NODE_ENV=production
– Giới hạn log nếu app ghi quá nhiều
– Backup database riêng nếu DB cùng VPS
– Pin version image nếu cần ổn định cao
– Theo dõi tài nguyên bằng htop, docker stats
Nếu traffic tăng:
– Nginx vẫn giữ vai trò proxy – Có thể scale nhiều API container – Sau đó cân nhắc Kubernetes/Swarm hoặc tách DB/cache sang managed service
Kết luận
Deploy NodeJS API lên VPS bằng Docker + Nginx reverse proxy là điểm cân bằng rất tốt giữa đơn giản và chuẩn production cơ bản. Docker giải quyết bài toán “máy em chạy được”, Nginx xử lý public traffic và HTTPS, còn VPS giúp tiết kiệm chi phí cho giai đoạn đầu.
Nếu bạn đang chạy NodeJS bằng cách SSH vào server rồi node app.js, đây là lúc nâng cấp. Chỉ cần thiết lập một lần, các lần deploy sau gần như còn đúng 2 lệnh: git pull và docker compose up -d --build.
Ít rủi ro hơn. Dễ debug hơn. Dễ mở rộng hơn. Với đa số API nhỏ và vừa, vậy là quá đủ để đi đường dài.
Bình luận (0)
Chưa có bình luận. Hãy là người đầu tiên!