Skip to content

BacMR Backend — Deployment

Production backend for https://api.bacmr.com, running on the host (185.185.83.156).

Architecture

Internet ──HTTPS──▶ nginx (aaPanel vhost, SSL)  api.bacmr.com
                        │  proxy_pass
                 backend container  :2026  (FastAPI / uvicorn)
                        │  SUPABASE_URL=http://host.docker.internal:8000
   ┌───────────── self-hosted Supabase stack (Docker) ─────────────┐
   │  Kong :8000 ─▶ GoTrue (auth) · PostgREST (rest) · Storage      │
   └───────────────────────────┬───────────────────────────────────┘
                                ▼  host.docker.internal:5432
                  host Postgres  bacmr_rag  (aaPanel PG16 + pgvector + pgcrypto)
  • Data lives in the external host Postgres bacmr_rag — survives any Docker wipe.
  • The Supabase services + backend are stateless containers (Storage file bytes are bind-mounted to infra/supabase/data/storage).
  • Every container is capped at 1 GB RAM (mem_limit: 1g).
  • First-time DB/roles/schema provisioning: see docs/95_plans/2026-06-28-self-host-supabase-external-db.md.

CI/CD (.github/workflows/deploy.yml)

Trigger: push to backend-rag (i.e. when PRs like #29 merge) + manual workflow_dispatch.

Flow (build-on-server, no registry): 1. SSH into the host (appleboy/ssh-action, port 47832, user mido). 2. git fetch + git reset --hard origin/backend-rag in /home/mido/Projects/BacMR/backend. 3. Render .env and infra/supabase/.env from GitHub secrets via envsubst (.env.production.tmpl, infra/supabase/.env.tmpl). 4. docker compose -f infra/supabase/docker-compose.yml up -d (stack). 5. docker compose up -d --build backend. 6. Health-check http://localhost:2026/health, then docker image prune -f.

The quality gate (test.yml) runs separately on the same branch.

GitHub secrets (repository-level)

Set via gh secret set. The existing Backend-RAG environment (cloud/elghidiya eval secrets) is intentionally left untouched — these self-hosted secrets are repo-level.

Secret Purpose
VPS_HOST / VPS_USER / VPS_PORT SSH target (185.185.83.156 / mido / 47832)
VPS_SSH_KEY private key of the deploy keypair (public key in mido's authorized_keys)
OPENAI_API_KEY PINECONE_API_KEY GOOGLE_TRANSLATE_API_KEY GOOGLE_TRANSLATE_PROJECT_ID app keys
JWT_SECRET ANON_KEY SERVICE_ROLE_KEY self-hosted Supabase keys (shared by backend + stack)
AUTHENTICATOR_PASSWORD AUTH_ADMIN_PASSWORD STORAGE_ADMIN_PASSWORD DB role passwords (must match db/supabase/00_roles.sql)

Rotate a value: printf '%s' "<new>" | gh secret set <NAME> --repo Lebjawi-Tech/BacMR (role passwords / JWT also need updating in the DB + a stack restart to stay in sync).

Reverse proxy (server-side, not in git)

aaPanel nginx vhost /www/server/panel/vhost/nginx/api.bacmr.com.conf — SSL is managed by aaPanel (Let's Encrypt, auto-renew). The proxy block added after #REWRITE-END:

location / {
    proxy_pass http://127.0.0.1:2026;
    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";
    proxy_connect_timeout 30s;
    proxy_read_timeout 300s;
}
Apply changes: sudo /www/server/nginx/sbin/nginx -t && sudo /www/server/nginx/sbin/nginx -s reload. A timestamped backup of the original vhost sits next to it (*.bak-*).

Manual deploy / rollback

cd /home/mido/Projects/BacMR/backend
# manual deploy of current branch
docker compose -f infra/supabase/docker-compose.yml --env-file infra/supabase/.env up -d
docker compose up -d --build backend

# rollback: check out a known-good commit and rebuild
git checkout <good-sha> && docker compose up -d --build backend

# logs / status
docker compose logs -f backend
docker compose -f infra/supabase/docker-compose.yml ps

Verified live

https://api.bacmr.com/health → 200; HTTP→HTTPS 301; POST /auth/signup and /auth/signin round-trip through nginx → backend → GoTrue → bacmr_rag; all five containers report a 1 GB memory limit.