Skip to content

Deployment and static SPAs

ScriptWhat it runs
npm run admin:buildturbo run build --filter=@indochina/admin-web
npm run vendor:buildturbo run build --filter=@indochina/vendor-web
npm run admin:build:stagingVite --mode staging + VITE_API_URL (see apps/admin-web/staging.env.example)
npm run vendor:build:stagingVite --mode staging + VITE_API_URL (see apps/vendor-web/staging.env.example)
npm run deploy:stagingdeploy-to-vps-staging.sh — staging SSH env, backend .env.host.staging, staging SPA builds
npm run backend:buildturbo run build --filter=@indochina/backend
npm run build:deployadmin:buildvendor:buildbackend:build (single command for release)
npm run deploybash deploy/deploy-to-vps.sh (optional VPS rsync; see below)

Vite outDir in each app’s vite.config.ts points to apps/backend/static/admin and apps/backend/static/vendor, so SPA builds write straight into the Nest static tree before backend:build.

The root Dockerfile:

  1. Builder: npm ci, copy workspaces, set NODE_ENV=production and VITE_API_URL=, run npm run build:deploy.
  2. Runner: production npm install --omit=dev for apps/backend only, copy dist and static, run node dist/main.js.

So CI “copy dist into static” is already encoded in the image build — no manual rsync inside the container.

  • Runtime: Nest writes gallery files under PUBLIC_STORAGE_ROOT (default {cwd}/public/storage) and serves them at /public/storage/* (mountPublicStorage in bootstrap-static-spas.ts). Clover import jobs use VENDOR_IMPORT_STORAGE_ROOT (default {cwd}/storage/vendor-imports).
  • Docker: deploy/docker-compose.yml mounts named volumes on those default paths so uploads survive image rebuilds.
  • PM2/VPS: set PUBLIC_STORAGE_ROOT and VENDOR_IMPORT_STORAGE_ROOT in apps/backend/.env.host to directories outside the rsync deploy tree; see deploy/README.mdPersistent uploads.
  • .github/workflows/docker-build.yml — validates the image on main/master and PRs (build only, no registry push).
  • deploy/deploy-to-vps.sh — builds locally (unless SKIP_BUILD=1), rsyncs static/* and/or dist to the server, optional RUN_MIGRATIONS=1, PM2 or systemd restart. Configure via deploy/.env.deploy.
  • When index.html exists under apps/backend/static/admin and static/vendor, the backend serves them (see bootstrap-static-spas.ts).
  • Reserved paths for the vendor SPA exclude /api, /admin, and Swagger UI/JSON paths so APIs keep working.

Document Nginx or CDN rules (TLS, caching, WebSocket if added) in your internal runbook.