# QuantDinger Cloud Server Deployment Guide

This guide covers production-style deployment on a cloud server with Docker Compose, domain setup, HTTPS, reverse proxy, and frontend/backend separation options.

## Recommended Architecture

Recommended setup: single domain + host Nginx reverse proxy

- Public URL: `https://app.example.com`
- Host Nginx: listens on `80/443`
- Docker `frontend`: binds to `127.0.0.1:8888`
- Docker `backend`: binds to `127.0.0.1:5000`
- Docker `postgres`: binds to `127.0.0.1:5432`

Benefits:

- Only `80/443` are exposed publicly
- Frontend and API stay on the same origin
- Backend and database are not directly exposed to the internet

## 1. Prepare the Server

Recommended:

- Ubuntu 22.04 / Debian 12
- 2 vCPU / 4 GB RAM or higher
- Security group ports open: `22`, `80`, `443`
- A domain such as `app.example.com`

DNS steps:

1. Create an `A` record
2. Host: `app`
3. Value: your server public IP
4. Wait for DNS propagation

Verify:

```bash
ping app.example.com
```

## 2. Install Docker and Docker Compose

Ubuntu / Debian example:

```bash
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
docker --version
docker compose version
```

If Docker Hub is slow or blocked in your network, you can switch image source later with `IMAGE_PREFIX` in the project-root `.env`.

## 3. Clone the Project

```bash
git clone https://github.com/brokermr810/QuantDinger.git
cd QuantDinger
```

## 4. Configure `backend_api_python/.env`

Copy the template:

```bash
cp backend_api_python/env.example backend_api_python/.env
```

Generate and write `SECRET_KEY`:

```bash
./scripts/generate-secret-key.sh
```

At minimum, review these values:

```ini
ADMIN_USER=quantdinger
ADMIN_PASSWORD=your_strong_password
SECRET_KEY=your_generated_secret
```

If you want AI features, add at least one provider key, for example:

```ini
OPENROUTER_API_KEY=your_key
```

## 5. Configure the Project-Root `.env`

The project-root `.env` is used by Docker Compose for ports and image source selection.

Copy the template:

```bash
cp .env.example .env
```

Recommended production values:

```ini
FRONTEND_PORT=127.0.0.1:8888
BACKEND_PORT=127.0.0.1:5000
DB_PORT=127.0.0.1:5432
IMAGE_PREFIX=
```

Explanation:

- `FRONTEND_PORT=127.0.0.1:8888`: only accessible locally, exposed through host Nginx
- `BACKEND_PORT=127.0.0.1:5000`: avoid exposing API directly
- `DB_PORT=127.0.0.1:5432`: avoid exposing PostgreSQL directly
- `IMAGE_PREFIX=`: empty means official Docker Hub

If image pulls fail, try:

```ini
IMAGE_PREFIX=docker.m.daocloud.io/library/
```

or:

```ini
IMAGE_PREFIX=docker.xuanyuan.me/library/
```

## 6. Start the Containers

```bash
docker-compose up -d --build
docker-compose ps
```

Logs:

```bash
docker-compose logs -f backend
docker-compose logs -f frontend
```

At this point, services usually listen on:

- `127.0.0.1:8888`
- `127.0.0.1:5000`
- `127.0.0.1:5432`

## 7. Install and Configure Nginx

Install:

```bash
sudo apt update
sudo apt install -y nginx
```

Recommended site config `/etc/nginx/sites-available/quantdinger.conf`:

```nginx
server {
    listen 80;
    server_name app.example.com;

    client_max_body_size 20m;

    location / {
        proxy_pass http://127.0.0.1:8888;
        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 it:

```bash
sudo ln -s /etc/nginx/sites-available/quantdinger.conf /etc/nginx/sites-enabled/quantdinger.conf
sudo nginx -t
sudo systemctl reload nginx
```

If using UFW:

```bash
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
```

## 8. Enable HTTPS with Let's Encrypt

Install Certbot:

```bash
sudo apt install -y certbot python3-certbot-nginx
```

Request the certificate:

```bash
sudo certbot --nginx -d app.example.com
```

Test renewal:

```bash
sudo certbot renew --dry-run
```

Then open:

```text
https://app.example.com
```

## 9. Recommended Production Mode: Single Domain

This is the recommended mode for the open-source edition.

Why:

- The open-source frontend is shipped as prebuilt `frontend/dist`
- The frontend container already proxies `/api/*` to `backend:5000` inside Docker
- Only one public domain and one TLS configuration are needed

Topology:

```text
Browser
  -> https://app.example.com
  -> Host Nginx :443
  -> 127.0.0.1:8888 (frontend container)
  -> /api/* then proxied by frontend container to backend:5000
```

## 10. Advanced Option: Frontend / Backend Separation

If you want:

- frontend: `app.example.com`
- API: `api.example.com`

you can use a dual-domain setup, but note:

1. the frontend must point API requests to `api.example.com`
2. backend cross-origin handling must be correct
3. this is better suited for deployments where you control frontend source/customization

Host Nginx can expose:

- `app.example.com` -> `127.0.0.1:8888`
- `api.example.com` -> `127.0.0.1:5000`

Example `api.example.com` config:

```nginx
server {
    listen 80;
    server_name api.example.com;

    client_max_body_size 20m;

    location / {
        proxy_pass http://127.0.0.1:5000;
        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;
    }
}
```

If you only want logical separation without cross-origin complexity, keep:

- `https://app.example.com/`
- `https://app.example.com/api/`

## 11. Common Operations

Check status:

```bash
docker-compose ps
```

View logs:

```bash
docker-compose logs -f backend
docker-compose logs -f frontend
docker-compose logs -f postgres
```

Update:

```bash
git pull
docker-compose up -d --build
```

Restart:

```bash
docker-compose restart backend
docker-compose restart frontend
```

Stop:

```bash
docker-compose down
```

## 12. Troubleshooting

### 1. Image pull failures

Symptoms:

- `failed to resolve source metadata`
- `registry-1.docker.io`
- `Docker Desktop has no HTTPS proxy`

Fix:

```ini
IMAGE_PREFIX=docker.m.daocloud.io/library/
```

Then rerun:

```bash
docker-compose up -d --build
```

### 2. Backend logs show `exec /usr/local/bin/docker-entrypoint.sh: no such file or directory`

Fix:

```bash
docker-compose build --no-cache backend
docker-compose up -d backend
```

### 3. Frontend logs show `host not found in upstream "backend"`

This usually means backend failed first.

Fix:

```bash
docker-compose ps
docker-compose logs backend --tail=100
docker-compose restart frontend
```

### 4. Frontend build fails with `COPY frontend/dist ... not found`

This usually means `.dockerignore` excluded `frontend/dist`, while the current open-source frontend image copies that prebuilt directory directly.

Check:

```bash
cat .dockerignore
ls frontend/dist
```

Make sure `.dockerignore` does NOT contain:

```text
frontend/dist
```

### 5. Saving settings fails with `Read-only file system: '/app/.env'`

This means `backend_api_python/.env` is mounted read-only into the container.

In `docker-compose.yml`, avoid:

```yaml
- ./backend_api_python/.env:/app/.env:ro
```

Use a writable mount instead:

```yaml
- ./backend_api_python/.env:/app/.env
```

Then run:

```bash
docker-compose up -d backend
```

### 6. Proxy works on host but not inside Docker

If your proxy listens on host `127.0.0.1:10808`, do not use `127.0.0.1` inside the container, because that points to the container itself.

For Docker deployments, use:

```ini
PROXY_URL=socks5h://host.docker.internal:10808
```

### 7. Exchange logs show `symbol not found`

If proxy/network access is already working but some symbols still fail, for example:

```text
Symbol 'MATIC/USDT' not found on okx
```

this is usually a market-symbol mapping / token-rename issue on the exchange side, not a general network failure.

### 8. Nginx 502 / 504

Check:

```bash
docker-compose ps
curl http://127.0.0.1:8888/health
curl http://127.0.0.1:5000/api/health
sudo nginx -t
```

### 9. PostgreSQL should not be public

Recommended:

```ini
DB_PORT=127.0.0.1:5432
```

Do not expose:

- `5432`
- `5000`

Publicly expose only:

- `80`
- `443`

