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