DeployEasy
Menu
DockerNâng cao

Hướng dẫn deploy REMAP bằng Docker lên VPS

Triển khai monorepo REMAP gồm NestJS, Next.js, React và PostgreSQL lên VPS Ubuntu bằng Docker Compose, Nginx, domain và SSL.

· 8 phút đọc · 1.668 từ
Mục lục bài viết

Tài liệu này hướng dẫn deploy project REMAP lên VPS Ubuntu bằng Docker Compose.

Phạm vi:

  • API: NestJS, workspace @remap/api, chạy cổng nội bộ 3000
  • User app: Next.js, workspace @remap/user, chạy cổng nội bộ 3001
  • Admin app: React + Vite, workspace @remap/admin
  • Database: PostgreSQL
  • Reverse proxy: Nginx container
  • SSL: Certbot trên VPS host

Ví dụ domain trong tài liệu:

User app:  https://remap.io.vn
Admin app: https://admin.remap.io.vn
API:       https://api.remap.io.vn/api

Tài liệu này không yêu cầu PM2 vì toàn bộ app sẽ chạy trong container.


1. Kiến trúc deploy

Mô hình khuyến nghị:

  • postgres: database nội bộ trong Docker network
  • api: build từ apps/api, nhận env production
  • user: build từ apps/user, public qua Nginx
  • admin: build static từ apps/admin, public qua Nginx
  • nginx: reverse proxy cho api, user, admin

Luồng truy cập:

Internet
  -> Nginx :80/:443
     -> api.remap.io.vn      -> api:3000
     -> remap.io.vn          -> user:3001
     -> admin.remap.io.vn    -> admin static

2. Chuẩn bị VPS

SSH vào VPS:

ssh root@YOUR_SERVER_IP

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

apt update && apt upgrade -y

Cài Docker, Compose plugin, Nginx, Certbot:

apt install -y ca-certificates curl gnupg nginx certbot python3-certbot-nginx
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker --version
docker compose version

Mở firewall:

ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable
ufw status

3. Clone source code

mkdir -p /var/www
cd /var/www
git clone YOUR_GITHUB_REPO_URL remap
cd remap

Project hiện có các workspace:

apps/api
apps/user
apps/admin
packages/api-client
packages/theme

4. Tạo file env production

API

Tạo file:

nano apps/api/.env.production

Nội dung mẫu:

NODE_ENV=production
API_PORT=3000
API_PREFIX=/api

DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_USER=remap
DATABASE_PASSWORD=CHANGE_ME_DB_PASSWORD
DATABASE_NAME=remap

JWT_ACCESS_SECRET=CHANGE_ME_LONG_RANDOM_ACCESS_SECRET
JWT_REFRESH_SECRET=CHANGE_ME_LONG_RANDOM_REFRESH_SECRET
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d

CORS_ORIGINS=https://remap.io.vn,https://admin.remap.io.vn

Lưu ý:

  • DATABASE_HOST=postgres vì API sẽ kết nối sang service Postgres trong Docker network.
  • CORS_ORIGINS phải khớp domain thật.

User app

Tạo file:

nano apps/user/.env.production

Nội dung:

NEXT_PUBLIC_API_BASE_URL=https://api.remap.io.vn/api

Admin app

Tạo file:

nano apps/admin/.env.production

Nội dung:

VITE_API_BASE_URL=https://api.remap.io.vn/api

5. Tạo Dockerfile cho từng app

Project hiện chưa có sẵn file Docker, nên cần thêm các file dưới đây vào repo.

apps/api/Dockerfile

FROM node:22-alpine AS builder
WORKDIR /app

COPY package*.json ./
COPY apps/api/package*.json apps/api/
COPY packages/api-client/package*.json packages/api-client/
COPY packages/theme/package*.json packages/theme/
RUN npm install

COPY . .
RUN npm run api:build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY package*.json ./
COPY apps/api/package*.json apps/api/
COPY packages/api-client/package*.json packages/api-client/
COPY packages/theme/package*.json packages/theme/
RUN npm install --omit=dev

COPY --from=builder /app/apps/api/dist apps/api/dist
COPY --from=builder /app/apps/api/.env.production apps/api/.env.production

WORKDIR /app/apps/api
EXPOSE 3000
CMD ["sh", "-c", "cp .env.production .env && node dist/main.js"]

apps/user/Dockerfile

FROM node:22-alpine AS builder
WORKDIR /app

COPY package*.json ./
COPY apps/user/package*.json apps/user/
COPY packages/api-client/package*.json packages/api-client/
COPY packages/theme/package*.json packages/theme/
RUN npm install

COPY . .
RUN cp apps/user/.env.production apps/user/.env
RUN npm run user:build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY package*.json ./
COPY apps/user/package*.json apps/user/
COPY packages/api-client/package*.json packages/api-client/
COPY packages/theme/package*.json packages/theme/
RUN npm install --omit=dev

COPY --from=builder /app/apps/user/.next apps/user/.next
COPY --from=builder /app/apps/user/public apps/user/public
COPY --from=builder /app/apps/user/src apps/user/src
COPY --from=builder /app/apps/user/next.config.* apps/user/
COPY --from=builder /app/apps/user/package*.json apps/user/

WORKDIR /app/apps/user
EXPOSE 3001
CMD ["npm", "start"]

apps/admin/Dockerfile

FROM node:22-alpine AS builder
WORKDIR /app

COPY package*.json ./
COPY apps/admin/package*.json apps/admin/
COPY packages/api-client/package*.json packages/api-client/
COPY packages/theme/package*.json packages/theme/
RUN npm install

COPY . .
RUN cp apps/admin/.env.production apps/admin/.env
RUN npm run admin:build

FROM nginx:stable-alpine
COPY --from=builder /app/apps/admin/dist /usr/share/nginx/html
EXPOSE 80

Lưu ý:

  • Vì đây là monorepo npm workspaces, Dockerfile phải copy cả root package.json và các package liên quan.
  • Với useradmin, biến môi trường public phải có trước bước build.

6. Tạo file Docker Compose

Tạo file:

nano docker-compose.yml

Nội dung mẫu:

services:
  postgres:
    image: postgres:16-alpine
    container_name: remap-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: remap
      POSTGRES_USER: remap
      POSTGRES_PASSWORD: CHANGE_ME_DB_PASSWORD
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - remap_net

  api:
    build:
      context: .
      dockerfile: apps/api/Dockerfile
    container_name: remap-api
    restart: unless-stopped
    depends_on:
      - postgres
    env_file:
      - apps/api/.env.production
    networks:
      - remap_net

  user:
    build:
      context: .
      dockerfile: apps/user/Dockerfile
    container_name: remap-user
    restart: unless-stopped
    depends_on:
      - api
    networks:
      - remap_net

  admin:
    build:
      context: .
      dockerfile: apps/admin/Dockerfile
    container_name: remap-admin
    restart: unless-stopped
    depends_on:
      - api
    networks:
      - remap_net

  nginx:
    image: nginx:stable-alpine
    container_name: remap-nginx
    restart: unless-stopped
    depends_on:
      - api
      - user
      - admin
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./deploy/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - remap_net

volumes:
  postgres_data:

networks:
  remap_net:

Ở cấu hình này:

  • container nginx chỉ expose ra 127.0.0.1:8080
  • Nginx trên host sẽ nhận traffic 80/443 và proxy sang 127.0.0.1:8080
  • cách này giúp Certbot trên host dễ cấp SSL hơn

7. Tạo config Nginx trong Docker

Tạo thư mục:

mkdir -p deploy/nginx
nano deploy/nginx/default.conf

Nội dung:

server {
    listen 80;
    server_name api.remap.io.vn;

    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;
    }
}

server {
    listen 80;
    server_name remap.io.vn www.remap.io.vn;

    location / {
        proxy_pass http://user:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
    }
}

server {
    listen 80;
    server_name admin.remap.io.vn;

    location / {
        proxy_pass http://admin:80;
        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;
    }
}

8. Tạo Nginx trên host để public ra Internet

Tạo file:

nano /etc/nginx/sites-available/remap

Nội dung:

server {
    listen 80;
    server_name api.remap.io.vn remap.io.vn www.remap.io.vn admin.remap.io.vn;

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

Kích hoạt:

ln -s /etc/nginx/sites-available/remap /etc/nginx/sites-enabled/remap
nginx -t
systemctl reload nginx

9. Build và chạy container

Từ root repo:

cd /var/www/remap
docker compose build
docker compose up -d
docker compose ps

Kiểm tra log:

docker compose logs -f api
docker compose logs -f user
docker compose logs -f admin
docker compose logs -f nginx

10. Chạy migration database

Sau khi container postgresapi đã lên, chạy migration:

cd /var/www/remap
docker compose exec api npm run migration:run
docker compose exec api npm run migration:show

Kiểm tra health:

curl http://127.0.0.1:8080/api/health -H "Host: api.remap.io.vn"

Kỳ vọng:

{"status":"ok","service":"api"}

11. Trỏ DNS

Tạo các bản ghi DNS:

Type: A
Name: @
Value: YOUR_SERVER_IP

Type: A
Name: www
Value: YOUR_SERVER_IP

Type: A
Name: api
Value: YOUR_SERVER_IP

Type: A
Name: admin
Value: YOUR_SERVER_IP

Sau khi DNS cập nhật, kiểm tra:

ping remap.io.vn
ping api.remap.io.vn
ping admin.remap.io.vn

12. Cấp SSL bằng Certbot

Chạy:

certbot --nginx \
  -d remap.io.vn \
  -d www.remap.io.vn \
  -d api.remap.io.vn \
  -d admin.remap.io.vn

Kiểm tra renew:

certbot renew --dry-run

13. Quy trình deploy lại

Khi có code mới:

ssh root@YOUR_SERVER_IP
cd /var/www/remap
git pull origin main
docker compose build
docker compose up -d
docker compose exec api npm run migration:run
docker compose ps

Nếu chỉ restart service:

docker compose restart api
docker compose restart user
docker compose restart admin
docker compose restart nginx

14. Backup database

Backup:

docker compose exec postgres pg_dump -U remap remap > /var/backups/remap-$(date +%F).sql

Restore:

cat /var/backups/remap-YYYY-MM-DD.sql | docker compose exec -T postgres psql -U remap -d remap

15. Lỗi thường gặp

15.1 API không kết nối được database

Kiểm tra:

docker compose logs api
docker compose logs postgres

Đảm bảo:

  • DATABASE_HOST=postgres
  • DATABASE_PORT=5432
  • mật khẩu ở apps/api/.env.production khớp với docker-compose.yml

15.2 Frontend vẫn gọi localhost

Nguyên nhân thường là build trước khi set env.

Cách xử lý:

docker compose build --no-cache user admin
docker compose up -d user admin

15.3 Migration không chạy

Kiểm tra container API đã đọc đúng env:

docker compose exec api printenv | grep DATABASE

Sau đó chạy lại:

docker compose exec api npm run migration:show
docker compose exec api npm run migration:run

15.4 Nginx trả 502

Kiểm tra service backend bên trong Docker:

docker compose ps
docker compose logs nginx
docker compose logs api
docker compose logs user
docker compose logs admin

15.5 Admin route refresh bị lỗi

Nếu muốn admin chạy qua reverse proxy đến container Nginx static như trên thì thường không lỗi. Nếu đổi sang cơ chế serve khác, cần đảm bảo SPA fallback về index.html.


16. Checklist hoàn thành

  • VPS đã cài Docker và Docker Compose
  • Repo đã clone vào /var/www/remap
  • apps/api/.env.production đã cấu hình đúng
  • apps/user/.env.production đã cấu hình đúng
  • apps/admin/.env.production đã cấu hình đúng
  • apps/api/Dockerfile đã tạo
  • apps/user/Dockerfile đã tạo
  • apps/admin/Dockerfile đã tạo
  • docker-compose.yml đã tạo
  • deploy/nginx/default.conf đã tạo
  • docker compose build chạy thành công
  • docker compose up -d chạy thành công
  • migration đã chạy xong
  • http://127.0.0.1:8080/api/health trả về ok
  • DNS đã trỏ đúng về VPS
  • SSL đã cấp thành công
  • https://remap.io.vn mở được
  • https://api.remap.io.vn/api/health mở được
  • https://admin.remap.io.vn mở được

Ghi chú thực tế

Tài liệu này bám theo cấu trúc code hiện tại của repo, nhưng repo chưa có sẵn Dockerfile và Compose file. Vì vậy có 2 cách triển khai:

  1. Chỉ dùng tài liệu này để thao tác thủ công trên VPS.
  2. Tạo luôn các file Docker vào repo rồi deploy đồng nhất giữa local, staging và production.

Nếu muốn, bước tiếp theo mình có thể tạo luôn bộ file Dockerfile, docker-compose.ymldeploy/nginx/default.conf ngay trong repo để tài liệu này chạy được trực tiếp.

Đọc tiếp