# Deployment

Docker Compose stacks for staging and production. **TLS and public routing use Apache2 on the host** with **Certbot** for Let's Encrypt.

| Environment | Domain | Compose file | Env file | Local ports (Apache proxy) |
|-------------|--------|--------------|----------|----------------------------|
| Staging | https://clinerax.wingsts.com | `docker-compose.staging.yml` | `.env.staging` (project root) | frontend `8081`, backend `8001` |
| Production | https://clinerax.com | `docker-compose.prod.yml` | `.env.production` (project root) | frontend `8080`, backend `8000` |

## Architecture

```text
Internet (80/443)
       │
 [Apache2]  TLS (certbot) + reverse proxy
       ├── /api/*  → 127.0.0.1:8001 (staging) or :8000 (prod)
       └── /*      → 127.0.0.1:8081 (staging) or :8080 (prod)
              │
         Docker Compose
              ├── frontend (serves React build)
              ├── backend
              ├── mysql (internal only)
              └── qdrant (internal only)
```

Sample Apache vhosts: `deploy/apache/clinerax.staging.conf` and `deploy/apache/clinerax.prod.conf`.

## Prerequisites

- Server with Docker, Docker Compose v2, **Apache2**, and **Certbot** (`certbot` + `python3-certbot-apache`)
- DNS A records:
  - `clinerax.wingsts.com` → staging server IP
  - `clinerax.com` and `www.clinerax.com` → production server IP
- Ports **80** and **443** open on the firewall

## Apache modules (one-time)

```bash
sudo a2enmod proxy proxy_http headers rewrite ssl
sudo systemctl reload apache2
```

## First-time setup (staging)

```bash
# 1. Env secrets (file must be next to docker-compose.staging.yml)
cp backend/.env.staging.example .env.staging
nano .env.staging
# Required for MySQL — these four must be set (not empty):
#   MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD

# 2. Start containers
docker compose --env-file .env.staging -f docker-compose.staging.yml up -d --build

# 3. Enable Apache site
sudo cp deploy/apache/clinerax.staging.conf /etc/apache2/sites-available/
sudo a2ensite clinerax.staging.conf
sudo apache2ctl configtest && sudo systemctl reload apache2

# 4. HTTPS with certbot (creates SSL vhost + HTTP→HTTPS redirect)
sudo certbot --apache -d clinerax.wingsts.com
```

Verify Docker is reachable before certbot:

```bash
curl -s http://127.0.0.1:8081/healthz
curl -s http://127.0.0.1:8001/api/v1/health
```

## First-time setup (production)

```bash
cp backend/.env.production.example .env.production
nano .env.production

docker compose --env-file .env.production -f docker-compose.prod.yml up -d --build

sudo cp deploy/apache/clinerax.prod.conf /etc/apache2/sites-available/
sudo a2ensite clinerax.prod.conf
sudo apache2ctl configtest && sudo systemctl reload apache2

sudo certbot --apache -d clinerax.com -d www.clinerax.com
```

Use **different** secrets, database names, and JWT keys for staging vs production.

If staging and production run on the **same server**, compose already uses different localhost ports (`8081`/`8001` vs `8080`/`8000`).

After certbot, edit the **`:443` VirtualHost** certbot created and set:

```apache
RequestHeader set X-Forwarded-Proto "https"
```

so the backend sees HTTPS correctly (if needed for redirects or cookies).

## Qdrant (manual embedding / snapshot restore)

After deploy, load Qdrant data yourself.

### Restore from a snapshot file

```bash
# 1. Copy snapshot into Qdrant's allowed snapshots directory (not /tmp)
docker cp /path/to/your.snapshot clinerax-staging-qdrant:/qdrant/snapshots/snapshot.snapshot

# 2. Recover (Qdrant image has no curl — use backend container's Node fetch)
docker exec clinerax-staging-backend node -e "
fetch('http://qdrant:6333/collections/dermatology_diseases/snapshots/recover', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ location: 'file:///qdrant/snapshots/snapshot.snapshot' }),
}).then(async (r) => console.log(r.status, await r.text()));
"

# 3. Verify point count
docker exec clinerax-staging-backend node -e "
fetch('http://qdrant:6333/collections/dermatology_diseases')
  .then((r) => r.json())
  .then((d) => console.log('points:', d.result?.points_count));
"
```

Collection name must match `QDRANT_COLLECTION` in `.env.staging` (default: `dermatology_diseases`).

For production, replace container names (`clinerax-prod-qdrant`, `clinerax-prod-backend`).

### Or run ingestion (PDF + OpenAI)

```bash
docker compose --env-file .env.staging -f docker-compose.staging.yml exec backend npm run ingest -- --reindex
```

Qdrant is not exposed on the host — only the backend container reaches it at `http://qdrant:6333`.

## Deploy updates

```bash
git pull
docker compose --env-file .env.staging -f docker-compose.staging.yml up -d --build
# or
docker compose --env-file .env.production -f docker-compose.prod.yml up -d --build
```

Rebuild the **frontend** image whenever `VITE_API_BASE_URL` or other build args change.

Certbot renews certificates automatically via systemd timer (`certbot renew`).

## Smoke tests

- https://clinerax.wingsts.com (or clinerax.com) loads the app
- https://clinerax.wingsts.com/api/v1/health returns `{"status":"ok",...}`
- Admin login works
- Diagnosis session completes (requires Qdrant data + `OPENAI_API_KEY`)

## Backups

```bash
docker compose -f docker-compose.staging.yml exec mysql \
  mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" clinerax_staging > backup.sql
```

## Troubleshooting

| Issue | Check |
|-------|--------|
| MySQL "password option is not specified" | `.env.staging` in **project root** (same folder as compose file) with `MYSQL_ROOT_PASSWORD` set |
| 502 / proxy error | Containers up? `curl http://127.0.0.1:8081` / `:8001/api/v1/health` |
| Certbot fails | DNS points to server; port 80 reachable; Apache site enabled |
| CORS errors | `APP_DOMAIN_NAME` must match browser URL (`https://...`) |
| Diagnosis empty | Qdrant populated; `OPENAI_API_KEY` set |
| Admin login fails | `ADMIN_PASSWORD` only applies on **first** boot |

## Security notes

- Never commit `backend/.env.staging` or `backend/.env.production`
- Change all `change-me-*` placeholders before go-live
- Compose binds app ports to `127.0.0.1` only — not publicly exposed
- phpMyAdmin is not included in stage/prod stacks
