Files
backend/docs/DEPLOY.md
T

7.2 KiB

Deploy Meshkee CMS API (Debian VM)

Stack: Docker (Postgres + Redis) → Node build on server → PM2 → Nginx + Let's Encrypt.

App path on server: /opt/meshkee/app

API domain: api.meshkee.comhttps://api.meshkee.com/api/v1

Per-business API aliases (same Nest app on this VM): api.{apex} e.g. api.sanihome.irhttps://api.sanihome.ir/api/v1. Storefronts still pass the website apex in paths (/tenants/sanihome.ir/...); only the API hostname changes.

Deploy path: push to git.meshkee.com (Meshkee/backend), then on the API VM git fetch + reset --hard origin/main + build + pm2 restart. Do not rsync the app as the normal update path. The API VM uses a read-only SSH deploy key (meshkee-api-vm-deploy). Pin sharp@0.33.5 — this VM CPU lacks x64-v2 required by sharp 0.35+.

Prerequisites

  • Debian VM with SSH access
  • Domain A record pointing at the VM (for HTTPS)
  • Git remote with this codebase (private repo → deploy key)
  • Production secrets (JWT, Postgres password, S3 keys)

1. Server packages

sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg git nginx ufw

# Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker "$USER"
# log out/in (or newgrp docker) so docker works without sudo

# Node.js 20 LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm install -g pm2

# Certbot (after Nginx is installed)
sudo apt install -y certbot python3-certbot-nginx

Firewall:

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

2. Clone the app

sudo mkdir -p /opt/meshkee
sudo chown "$USER:$USER" /opt/meshkee
cd /opt/meshkee
git clone <YOUR_GIT_REMOTE_URL> app
cd app

Private repo: create an SSH deploy key on the VM (ssh-keygen -t ed25519 -C "meshkee-deploy"), add the public key as a read-only deploy key on GitHub/GitLab, clone via SSH URL.

3. Production env

cp .env.example .env
nano .env   # set strong secrets — never commit this file

Required production values:

  • Strong POSTGRES_PASSWORD and matching DATABASE_URL
  • Long random JWT_ACCESS_SECRET / JWT_REFRESH_SECRET
  • Real S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY
  • PORT=3000
  • SMS_ENABLED as needed

4. Database + Redis

cd /opt/meshkee/app
docker compose up -d
docker compose ps

First Postgres volume init runs SQL under database/migrations/ automatically.

Later schema updates:

./database/migrate.sh

5. Build and run (on the server)

cd /opt/meshkee/app
npm ci
npm run prisma:generate
npm run build

Production seed (super admin only — skip sample data):

./database/seed.sh database/seeds/002_super_admin_user.sql
# optional reference data:
# ./database/seed.sh database/seeds/004_iran_cities.sql
# ./database/seed.sh database/seeds/005_business_categories.sql

Start with PM2:

pm2 start ecosystem.config.js
pm2 save
pm2 startup   # run the command it prints (usually with sudo)

Health check locally on the VM:

curl -s http://127.0.0.1:3000/api/v1/ | head
# or hit a known public route such as tenant resolve

6. Nginx + HTTPS

DNS (per business website domain)

On the business domain DNS (e.g. zone sanihome.ir), add a subdomain that points at this API VM (same target as api.meshkee.com):

Type Name / host Value Notes
A (preferred) api <API_VM_PUBLIC_IP> Resolves api.sanihome.ir → API server
CNAME (alternative) api api.meshkee.com Same effect if your DNS panel allows CNAME on subdomains

Do not point api.{apex} at the websites VM or dashboards VM — only the Nest API VM.

Repeat for each storefront apex (api.ali-mohammadi.ir, etc.). Central Meshkee DNS already has api.meshkee.com → this VM.

Nginx

Create /etc/nginx/sites-available/meshkee-api:

server {
    listen 80;
    # Central + per-business aliases (add more api.{apex} as domains go live)
    server_name api.meshkee.com api.sanihome.ir;

    client_max_body_size 15M;

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

Enable and get certificates:

sudo ln -sf /etc/nginx/sites-available/meshkee-api /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.meshkee.com -d api.sanihome.ir
# later, when another business goes live:
# sudo certbot --nginx -d api.meshkee.com -d api.sanihome.ir -d api.other-site.ir

API base URLs (identical Nest routes):

  • Central: https://api.meshkee.com/api/v1
  • Alias example: https://api.sanihome.ir/api/v1

After deploy, these are public (no auth):

  • Hub: https://api.meshkee.com/docs/website
  • OpenAPI: https://api.meshkee.com/docs/website/openapi.json
  • Postman: https://api.meshkee.com/docs/website/Meshkee-Website-API.postman_collection.json
  • AI brief: https://api.meshkee.com/docs/website/AI_PROMPT.md

Files live in docs/website-api/ and are served by Nest from process cwd. Keep that folder on the VM when deploying.

Automated cert host list (API VPS)

After deploy, a sync agent on this VM can pull names to cover:

curl -s -H "X-SSL-Sync-Token: $SSL_SYNC_TOKEN" \
  https://api.meshkee.com/api/v1/internal/ssl/api-hosts
# → { "hosts": ["api.ali-mohammadi.ir", "api.meshkee.com", "api.sanihome.ir", ...] }

Dashboards VPS keeps using GET /api/v1/internal/ssl/hosts (business. / customer. / manage) — do not mix the two lists.

Ongoing updates (git only)

From your laptop:

  1. Commit and push to origin/main (git.meshkee.com/Meshkee/backend).
  2. Deploy on the API VM from that commit (never rsync the tree as the primary path):
ssh -i ~/.ssh/id_ed25519 root@185.164.72.119 'bash -s' <<'REMOTE'
set -euo pipefail
cd /opt/meshkee/app
git fetch origin
git reset --hard origin/main
./database/migrate.sh
npm ci
npm run prisma:generate
npm run build
pm2 restart meshkee-api
REMOTE

Preserve /opt/meshkee/app/.env on the server. App remote must be SSH: git@git.meshkee.com:Meshkee/backend.git with the VM deploy key registered as a read-only deploy key on the repo.

Gitea SSH is on port 222. On the API VM, /root/.ssh/config must set Port 222 for Host git.meshkee.com (system SSH on 22 is not Gitea).

Useful commands

pm2 status
pm2 logs meshkee-api
docker compose logs -f postgres