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.
Mục lục bài viết
VPS & Hosting cho dự án của bạn
Khám phá các gói máy chủ và lưu trữ phù hợp từ website cá nhân đến ứng dụng production.
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 networkapi: build từapps/api, nhận env productionuser: build từapps/user, public qua Nginxadmin: build static từapps/admin, public qua Nginxnginx: reverse proxy choapi,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=postgresvì API sẽ kết nối sang service Postgres trong Docker network.CORS_ORIGINSphả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.jsonvà các package liên quan. - Với
uservàadmin, 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
nginxchỉ expose ra127.0.0.1:8080 - Nginx trên host sẽ nhận traffic
80/443và proxy sang127.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 postgres và api đã 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=postgresDATABASE_PORT=5432- mật khẩu ở
apps/api/.env.productionkhớp vớidocker-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 buildchạy thành công -
docker compose up -dchạy thành công - migration đã chạy xong
-
http://127.0.0.1:8080/api/healthtrả vềok - DNS đã trỏ đúng về VPS
- SSL đã cấp thành công
-
https://remap.io.vnmở được -
https://api.remap.io.vn/api/healthmở được -
https://admin.remap.io.vnmở đượ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:
- Chỉ dùng tài liệu này để thao tác thủ công trên VPS.
- 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.yml và deploy/nginx/default.conf ngay trong repo để tài liệu này chạy được trực tiếp.
Đọc tiếp
Bài viết liên quan
Docker Compose production: App, PostgreSQL, healthcheck và backup
Hướng dẫn thiết kế Docker Compose production với app, PostgreSQL, network, volume, healthcheck, secret, restart và deploy an toàn.
Đọc bài →Build Docker trên local rồi deploy lên VPS
Quy trình từng bước tạo Dockerfile, chạy thử monorepo REMAP trên local và dùng cùng cấu hình Docker Compose để triển khai lên VPS.
Đọc bài →Docker là gì? Image, container, volume và Docker Compose
Hướng dẫn Docker từ cơ bản: container hoạt động thế nào, image và container khác gì, Dockerfile, volume, network và Docker Compose.
Đọc bài →