Chạy Next.js trên VPS với Docker: Gọn, nhanh, dễ mở rộng

11/05/2026 · P T P · Chung

Hướng dẫn chạy Next.js trên VPS với Docker: Dễ quản lý, dễ mở rộng

Triển khai Next.js lên VPS thường bắt đầu khá đơn giản: cài Node.js, clone source, chạy npm install, build, dùng PM2. Nhưng khi dự án lớn hơn, nhiều môi trường hơn, nhiều service hơn — database, Redis, reverse proxy, SSL — cách làm thủ công bắt đầu khó kiểm soát.

Docker giải quyết vấn đề đó bằng cách đóng gói ứng dụng cùng môi trường chạy. Thay vì phụ thuộc vào VPS đang cài Node version nào, package nào, biến môi trường nào, ta định nghĩa tất cả trong Dockerfiledocker-compose.yml. Kết quả: deploy ổn định hơn, rollback dễ hơn, mở rộng thuận tiện hơn.

Bài viết này hướng dẫn cách chạy Next.js trên VPS bằng Docker theo hướng thực tế, dễ bảo trì.


Vì sao nên chạy Next.js bằng Docker trên VPS?

Môi trường nhất quán

Next.js phụ thuộc khá nhiều vào phiên bản Node.js, package manager, biến môi trường, build output. Nếu dev dùng Node 20 nhưng VPS dùng Node 18, lỗi có thể xuất hiện.

Docker giúp cố định môi trường:

– Node.js version cụ thể
– Lệnh build rõ ràng
– Runtime tách biệt
– Không “bẩn” hệ thống VPS

Code chạy được trong container local → gần như chắc chắn chạy được trên VPS.

Deploy dễ hơn

Thay vì cài thủ công nhiều thứ trên server, bạn chỉ cần:

docker compose up -d --build

Muốn restart:

docker compose restart

Muốn xem log:

docker compose logs -f

Rất gọn.

Dễ mở rộng

Khi cần thêm PostgreSQL, Redis, Nginx, worker, queue, bạn chỉ cần bổ sung service trong docker-compose.yml.

Ví dụ:

app: Next.js
db: PostgreSQL
redis: cache/session
nginx: reverse proxy
watchtower: auto update image

Kiến trúc rõ ràng → quản lý dễ.


Chuẩn bị VPS

Bạn cần một VPS Linux, thường dùng Ubuntu 22.04 hoặc 24.04.

Yêu cầu tối thiểu:

– RAM: 1GB cho app nhỏ, nên 2GB+
– CPU: 1 vCPU trở lên
– Disk: 20GB+
– Domain trỏ về IP VPS
– SSH access

Cập nhật hệ thống:

sudo apt update && sudo apt upgrade -y

Cài Docker:

curl -fsSL https://get.docker.com | sh

Thêm user hiện tại vào group Docker:

sudo usermod -aG docker $USER

Đăng xuất SSH rồi đăng nhập lại.

Kiểm tra:

docker --version
docker compose version

Cấu trúc project Next.js

Ví dụ project:

my-next-app/
├── app/
├── public/
├── package.json
├── next.config.js
├── Dockerfile
├── docker-compose.yml
└── .dockerignore

Nếu dùng Next.js App Router hoặc Pages Router đều được. Điểm quan trọng là build ra production server.

Trong package.json, cần có scripts:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

Tạo .dockerignore

File này giúp Docker không copy những thứ không cần thiết vào image.

node_modules
.next
.git
Dockerfile
docker-compose.yml
npm-debug.log
.env
.env.local

Lưu ý: không copy .env vào image. Nên truyền biến môi trường qua Docker Compose hoặc secret.


Viết Dockerfile cho Next.js

Dùng multi-stage build để image nhẹ hơn.

FROM node:20-alpine AS deps
WORKDIR /app

COPY package*.json ./ RUN npm ci

FROM node:20-alpine AS builder WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules COPY . .

RUN npm run build

FROM node:20-alpine AS runner WORKDIR /app

ENV NODE_ENV=production

COPY --from=builder /app/package*.json ./ COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/next.config.js ./next.config.js

EXPOSE 3000

CMD ["npm", "start"]

Cách này đơn giản, dễ hiểu. Tuy image chưa tối ưu tối đa, nhưng phù hợp nhiều dự án.

Nếu muốn nhẹ hơn nữa, có thể dùng output: 'standalone' trong next.config.js.

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone'
}

module.exports = nextConfig

Khi dùng standalone, Dockerfile sẽ khác một chút, image nhỏ hơn, runtime ít dependency hơn.


Tạo docker-compose.yml

Cấu hình cơ bản:

services:
  next-app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: next-app
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      NEXT_PUBLIC_SITE_URL: https://example.com

Chạy app:

docker compose up -d --build

Kiểm tra container:

docker ps

Xem log:

docker compose logs -f next-app

Nếu mọi thứ ổn, app sẽ chạy tại:

http://your-server-ip:3000

Dùng Nginx reverse proxy

Không nên public trực tiếp port 3000 cho production. Nên dùng Nginx đứng trước app để:

– Gắn domain
– Cài SSL
– Proxy request
– Nén gzip/brotli
– Quản lý nhiều app trên cùng VPS

Cài Nginx:

sudo apt install nginx -y

Tạo config:

sudo nano /etc/nginx/sites-available/next-app

Nội dung:

server {
    listen 80;
    server_name example.com www.example.com;

location / { proxy_pass http://127.0.0.1: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;

proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }

Kích hoạt:

sudo ln -s /etc/nginx/sites-available/next-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Lúc này domain sẽ trỏ vào app Docker.


Cài SSL với Certbot

SSL gần như bắt buộc cho production.

Cài Certbot:

sudo apt install certbot python3-certbot-nginx -y

Cấp chứng chỉ:

sudo certbot --nginx -d example.com -d www.example.com

Kiểm tra auto renew:

sudo certbot renew --dry-run

Sau bước này, website chạy qua HTTPS.


Quản lý biến môi trường

Không hardcode key trong source. Dùng file .env.production trên VPS hoặc khai báo trong docker-compose.yml.

Ví dụ:

services:
  next-app:
    build: .
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    env_file:
      - .env.production

File .env.production:

NODE_ENV=production
NEXT_PUBLIC_SITE_URL=https://example.com
DATABASE_URL=postgresql://user:password@db:5432/appdb

Lưu ý:

– Biến bắt đầu bằng NEXT_PUBLIC_ sẽ xuất hiện phía client
– Secret như DATABASE_URL, JWT_SECRET, API_SECRET không dùng prefix này
– Không commit .env.production lên Git


Thêm PostgreSQL bằng Docker Compose

Nếu app cần database, thêm service db.

services:
  next-app:
    build: .
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    env_file:
      - .env.production
    depends_on:
      - db

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

volumes: postgres_data:

Volume postgres_data giúp dữ liệu không mất khi container bị xóa.

Khi deploy thực tế, hãy dùng password mạnh, backup định kỳ, hạn chế mở port DB ra ngoài.


Quy trình deploy thực tế

Một quy trình đơn giản:

cd /var/www/my-next-app
git pull origin main
docker compose up -d --build
docker image prune -f

Nếu dùng migration database:

docker compose exec next-app npx prisma migrate deploy

Hoặc chạy migration trước khi restart app, tùy kiến trúc.

Có thể tạo script deploy.sh:

#!/bin/bash
set -e

git pull origin main docker compose up -d --build docker image prune -f

Phân quyền:

chmod +x deploy.sh

Chạy:

./deploy.sh

Giám sát log và xử lý lỗi

Xem log realtime:

docker compose logs -f

Xem riêng app:

docker compose logs -f next-app

Restart app:

docker compose restart next-app

Vào trong container:

docker compose exec next-app sh

Kiểm tra tài nguyên:

docker stats

Một số lỗi thường gặp:

Port đã bị chiếm

Bind for 0.0.0.0:3000 failed: port is already allocated

Cách xử lý: kiểm tra process/container đang dùng port.

sudo lsof -i :3000
docker ps

Build lỗi do thiếu env

Next.js có thể cần env ngay lúc build. Khi đó cần truyền env trong build stage hoặc đảm bảo biến tồn tại trước khi npm run build.

App chạy local nhưng lỗi trên VPS

Thường do:

– Sai biến môi trường
– Node version khác
– Database chưa sẵn sàng
– Domain/API URL sai
– Thiếu quyền file upload

Docker giảm phần lớn lỗi môi trường, nhưng không thay thế việc cấu hình đúng.


Tối ưu bảo mật cơ bản

Một vài việc nên làm ngay:

– Chỉ expose app qua localhost:

ports:
  - "127.0.0.1:3000:3000"

– Dùng Nginx làm reverse proxy
– Bật firewall:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

– Không mở port PostgreSQL public
– Không commit .env
– Cập nhật image định kỳ
– Backup volume database
– Dùng SSH key thay password login


Khi nào cần mở rộng?

Với một VPS, Docker Compose đủ tốt cho nhiều website nhỏ và vừa. Khi traffic tăng, có thể mở rộng theo các hướng:

– Tăng cấu hình VPS
– Tách database sang server riêng
– Dùng Redis cache
– Đưa static asset lên CDN/S3
– Chạy nhiều replica sau load balancer
– Chuyển sang Kubernetes nếu hệ thống đủ lớn

Không nên phức tạp hóa sớm. Docker Compose là bước cân bằng tốt giữa đơn giản và chuyên nghiệp.


Kết luận

Chạy Next.js trên VPS với Docker giúp quá trình deploy rõ ràng, ổn định, dễ lặp lại. Bạn không còn phải phụ thuộc vào môi trường cài thủ công trên server. Mọi thứ — Node.js, build, runtime, database, biến môi trường — đều được định nghĩa bằng file cấu hình.

Cách triển khai thực tế nên gồm:

– Next.js chạy trong Docker container
– Nginx reverse proxy phía trước
– SSL bằng Certbot
– Env quản lý ngoài source
– Database dùng volume riêng
– Deploy qua docker compose up -d --build
– Log, backup, firewall đầy đủ

Với setup này, bạn có nền tảng đủ gọn cho dự án nhỏ, đủ chắc cho production, và đủ linh hoạt để mở rộng khi sản phẩm phát triển.

#chay #docker #next #nhanh #tren
Chia sẻ:
← Trước
7 Bí Quyết Bảo Mật Next.js Trên VPS Cho Website Doanh Nghiệp

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!