451 lines
8.1 KiB
Markdown
451 lines
8.1 KiB
Markdown
# 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`
|
||
|
|
|