Triển khai Next.js và NestJS lên VPS bằng Docker: Từ source code đến production - Basic
System Architecture, DevOps

Tags: Docker, Next.js, NestJS, DevOps, Linux
1. Kiến trúc triển khai
Một ứng dụng web fullstack thường có ít nhất ba thành phần: Đây là một kiến trúc cơ bản, phổ biến, dễ triển khai. Các bạn sinh viên có thể lấy làm mẫu để học deploy.
- Frontend: Next.js, chịu trách nhiệm giao diện và rendering.
- Backend: NestJS, cung cấp REST API hoặc các dịch vụ nghiệp vụ.
- Database: PostgreSQL, lưu trữ dữ liệu ứng dụng.
Thay vì cài đặt Node.js và chạy từng ứng dụng trực tiếp trên VPS, chúng ta có thể đóng gói từng thành phần bằng Docker và quản lý chúng thông qua Docker Compose.
Kiến trúc mục tiêu:
Internet
|
HTTPS
|
Nginx / Proxy
|
+---------+---------+
| |
Next.js :3000 NestJS :4000
| |
+-------------------+
|
PostgreSQL
Trong môi trường production, database không nên được công khai trực tiếp ra Internet. Các dịch vụ nội bộ chỉ nên giao tiếp qua Docker network khi cần thiết.
Nginx hoặc một reverse proxy tương đương đảm nhiệm việc tiếp nhận kết nối HTTP/HTTPS và định tuyến đến các ứng dụng.
2. Tổ chức source code
Ví dụ cấu trúc repository:
my-app/
├── frontend/
│ ├── app/
│ ├── package.json
│ └── Dockerfile
├── backend/
│ ├── src/
│ ├── package.json
│ └── Dockerfile
├── compose.yaml
└── .dockerignore
Tách frontend và backend giúp mỗi ứng dụng có quy trình build, cấu hình và vòng đời triển khai riêng.
Trong một dự án lớn hơn, bạn có thể quản lý chúng bằng monorepo hoặc hai repository độc lập. Lựa chọn phụ thuộc vào cách tổ chức đội ngũ, quyền truy cập và quy trình phát hành.
3. Dockerfile cho NestJS
Ví dụ dưới đây giả định backend sử dụng npm, TypeScript và tạo ra thư mục dist sau khi build.
# backend/Dockerfile
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine AS production
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 4000
CMD ["node", "dist/main.js"]
Dockerfile sử dụng multi-stage build để tách môi trường build khỏi môi trường chạy.
Một số điểm cần chú ý:
npm cicài dependencies theo lockfile.- Stage production không cần compiler TypeScript nếu ứng dụng đã được build thành JavaScript.
NODE_ENV=productiongiúp ứng dụng nhận biết môi trường chạy.- Chạy bằng user không phải root giúp giảm rủi ro khi container bị khai thác.
Nếu ứng dụng cần Prisma Client, file cấu hình, thư mục upload hoặc các tài nguyên khác, bạn phải bổ sung chúng vào image theo đúng cách ứng dụng sử dụng.
4. Dockerfile cho Next.js
Đối với Next.js, có thể sử dụng chế độ standalone để tạo image production gọn hơn.
Trong next.config.ts:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
};
export default nextConfig;
Dockerfile:
# frontend/Dockerfile
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
COPY --from=build --chown=node:node \
/app/.next/standalone ./
COPY --from=build --chown=node:node \
/app/.next/static ./.next/static
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Cấu hình này giả định phiên bản Next.js và cấu trúc dự án hỗ trợ standalone output.
Nếu ứng dụng sử dụng thư mục public, hãy sao chép thêm thư mục đó khi cần. Với monorepo, vị trí standalone output có thể khác và cần điều chỉnh Dockerfile.
5. Quản lý các service bằng Docker Compose
Tạo file compose.yaml:
services:
frontend:
build:
context: ./frontend
restart: unless-stopped
environment:
PORT: "3000"
HOSTNAME: "0.0.0.0"
expose:
- "3000"
networks:
- app-network
backend:
build:
context: ./backend
restart: unless-stopped
environment:
NODE_ENV: production
PORT: "4000"
DATABASE_URL: ${DATABASE_URL}
expose:
- "4000"
networks:
- app-network
networks:
app-network:
driver: bridge
Đây là cấu hình minh họa cho hai ứng dụng, chưa phải toàn bộ stack production. Database và reverse proxy có thể được quản lý trong cùng Compose project hoặc tách riêng tùy nhu cầu vận hành.
Các điểm quan trọng:
restart: unless-stoppedgiúp container được khởi động lại trong những trường hợp phù hợp.exposecho phép khai báo cổng phục vụ giao tiếp nội bộ, nhưng không tự công khai cổng ra host.DATABASE_URLđược lấy từ biến môi trường thay vì hardcode thông tin kết nối vào source code.- Docker network cho phép các service giao tiếp với nhau bằng tên service.
Lưu ý: biến môi trường trong Compose không tự động trở thành secret an toàn. Không commit file .env chứa mật khẩu thật lên Git.
6. Build và khởi chạy
Trên máy có source code:
docker compose build
docker compose up -d
Kiểm tra trạng thái:
docker compose ps
docker compose logs --tail=100 backend
docker compose logs --tail=100 frontend
Nếu ứng dụng không khởi động, hãy kiểm tra log trước khi thay đổi cấu hình một cách tùy tiện.
Một số nguyên nhân phổ biến:
- Sai biến môi trường.
- Backend không lắng nghe đúng port hoặc interface.
- Database chưa sẵn sàng khi ứng dụng khởi động.
- Thiếu file cần thiết trong Docker image.
- Lỗi migration hoặc kết nối database.
- Next.js không tìm thấy file runtime cần thiết.
Không nên mặc định rằng container đang chạy đồng nghĩa với ứng dụng hoạt động bình thường. Health check và kiểm tra endpoint thực tế sẽ cung cấp thêm bằng chứng về tình trạng hệ thống.
7. Những điều cần bổ sung trước khi đưa lên production
Một cấu hình Docker chạy được chưa đồng nghĩa với một quy trình production hoàn chỉnh.
Reverse proxy và HTTPS
Cấu hình Nginx hoặc reverse proxy tương đương để tiếp nhận HTTPS và chuyển tiếp request đến frontend hoặc backend.
Chỉ công khai các cổng cần thiết. Database và các service nội bộ không nên mở trực tiếp cho Internet.
Database và backup
Sử dụng volume phù hợp để lưu dữ liệu lâu dài. Thiết lập lịch backup, xác định thời gian lưu trữ và định kỳ thử phục hồi dữ liệu.
Backup chỉ thực sự đáng tin khi quá trình restore đã được kiểm chứng.
Logging và monitoring
Theo dõi log ứng dụng, mức sử dụng CPU, RAM, disk, dung lượng database và các lỗi HTTP quan trọng.
Đặt cảnh báo cho những chỉ số có thể ảnh hưởng trực tiếp đến người dùng.
Quy trình phát hành
Một quy trình CI/CD tốt nên có các bước:
- Kiểm tra code và chạy test.
- Build Docker image.
- Gắn tag image bằng commit SHA hoặc phiên bản phát hành.
- Đẩy image lên container registry.
- Máy chủ production tải image đã build và triển khai.
- Kiểm tra sức khỏe ứng dụng sau triển khai.
- Rollback về phiên bản trước nếu phát hành thất bại.
Với môi trường yêu cầu kiểm soát chặt chẽ, nên để CI build image và VPS chỉ pull image đã được tạo sẵn, thay vì build source code trực tiếp trên máy chủ production.
Kết luận
Docker giúp chuẩn hóa môi trường chạy ứng dụng, nhưng bản thân Docker không giải quyết toàn bộ bài toán vận hành.
Một hệ thống production cần sự kết hợp giữa image được build có kiểm soát, cấu hình an toàn, networking hợp lý, logging, monitoring, backup và quy trình phát hành có khả năng phục hồi.
Mục tiêu cuối cùng không chỉ là đưa ứng dụng lên VPS thành công. Đó là xây dựng một hệ thống có thể triển khai lặp lại, kiểm tra được và bảo trì lâu dài.
Một triển khai tốt là triển khai mà đội ngũ có thể hiểu, kiểm soát và phục hồi — không chỉ là triển khai chạy được một lần.