BacMR Backend — Deployment
Production backend for https://api.bacmr.com, running on the host (185.185.83.156).
Architecture
Internet ──HTTPS──▶ nginx (aaPanel vhosts, SSL)
api.bacmr.com ─▶ backend container :2026 (FastAPI / uvicorn)
docs.bacmr.com ─▶ docs container :8055 (static MkDocs)
supabase.bacmr.com ─▶ Kong :8000 (PUBLIC Supabase gateway — for UI / mobile clients)
│
backend ──(SUPABASE_URL=http://host.docker.internal:8000, INTERNAL)──┐
▼ ▼
┌───────────── self-hosted Supabase stack (Docker) ─────────────┐
│ Kong :8000 ─▶ GoTrue (auth) · PostgREST (rest) · Storage │
└───────────────────────────┬───────────────────────────────────┘
▼ host.docker.internal:5432
host Postgres bacmr_rag (aaPanel PG16 + pgvector + pgcrypto)
Supabase URLs — internal vs public (important)
- Backend → Supabase:
SUPABASE_URL=http://host.docker.internal:8000— the internal Kong gateway. Server-side calls stay on the host: faster (no public round-trip / TLS handshake) and resilient to nginx/DNS issues. This is the correct setting for the backend — do NOT point it at the public URL. - External clients (UI / mobile) → Supabase:
https://supabase.bacmr.com— an aaPanel vhost (SSL) reverse-proxying to the same Kong:8000. Health check:https://supabase.bacmr.com/auth/v1/health→ GoTrue200. -
GoTrue is configured with
API_EXTERNAL_URL=https://supabase.bacmr.com(so issued tokens and auth links reference the public domain) andSITE_URL=https://bacmr.com. Tokens are validated by signature (sharedJWT_SECRET), so the backend validating them over the internal route works regardless of the token'siss. -
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-rag.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 /opt/bacmr (the dedicated deploy
checkout — separate from any dev clone).
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 (ci-rag.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: edit it in Infisical (sops.bacmr.com, project bacmr-rag-engine, env dev) and redeploy — the deploy renders .env via deploy/refresh-env.sh.
(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;
}
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.