Fix production auth by proxying /api to backend in remote builds.
Remote frontend nginx now forwards API requests to Django so login and registration work when the host proxy only routes to port 8080. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,21 +11,23 @@
|
|||||||
# sudo apache2ctl configtest && sudo systemctl reload apache2
|
# sudo apache2ctl configtest && sudo systemctl reload apache2
|
||||||
|
|
||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
ServerName your-domain.com
|
ServerName social.deepfishing.ru
|
||||||
ServerAlias www.your-domain.com
|
|
||||||
|
|
||||||
ProxyPreserveHost On
|
ProxyPreserveHost On
|
||||||
RequestHeader set X-Forwarded-Proto "http"
|
RequestHeader set X-Forwarded-Proto "https"
|
||||||
RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
|
RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
|
||||||
|
|
||||||
# Backend API (раскомментируйте при VITE_DATA_MODE=remote и profile with-backend)
|
# Вариант A (рекомендуется): весь трафик на frontend-контейнер.
|
||||||
# ProxyPass /api http://127.0.0.1:8000/api
|
# В remote-сборке frontend сам проксирует /api → backend.
|
||||||
# ProxyPassReverse /api http://127.0.0.1:8000/api
|
|
||||||
|
|
||||||
# Frontend SPA (nginx в контейнере sg_frontend)
|
|
||||||
ProxyPass / http://127.0.0.1:8080/
|
ProxyPass / http://127.0.0.1:8080/
|
||||||
ProxyPassReverse / http://127.0.0.1:8080/
|
ProxyPassReverse / http://127.0.0.1:8080/
|
||||||
|
|
||||||
|
# Вариант B: проксировать /api напрямую на backend (если frontend без nginx.remote.conf)
|
||||||
|
# ProxyPass /api http://127.0.0.1:8000/api
|
||||||
|
# ProxyPassReverse /api http://127.0.0.1:8000/api
|
||||||
|
# ProxyPass / http://127.0.0.1:8080/
|
||||||
|
# ProxyPassReverse / http://127.0.0.1:8080/
|
||||||
|
|
||||||
ErrorLog ${APACHE_LOG_DIR}/social-graph-error.log
|
ErrorLog ${APACHE_LOG_DIR}/social-graph-error.log
|
||||||
CustomLog ${APACHE_LOG_DIR}/social-graph-access.log combined
|
CustomLog ${APACHE_LOG_DIR}/social-graph-access.log combined
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|||||||
@@ -116,12 +116,15 @@ cd /opt/social-graph
|
|||||||
|
|
||||||
### Remote (frontend + backend)
|
### Remote (frontend + backend)
|
||||||
|
|
||||||
|
> Обязательно: `VITE_DATA_MODE=remote` в `.env.prod` и профиль `with-backend` — иначе вход/регистрация не работают (ошибка 405).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod build
|
docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod build
|
||||||
docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod up -d --build frontend
|
docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod --profile with-backend up -d --build
|
||||||
docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod --profile with-backend up -d --build backend
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Команда поднимает **оба** контейнера. Frontend в remote-сборке проксирует `/api` → backend внутри Docker.
|
||||||
|
|
||||||
### Только frontend (local)
|
### Только frontend (local)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -320,8 +323,9 @@ sudo ufw enable
|
|||||||
|
|
||||||
| Симптом | Решение |
|
| Симптом | Решение |
|
||||||
|---------|---------|
|
|---------|---------|
|
||||||
|
| **405 Not Allowed** при входе/регистрации | Запросы `/api` попали во frontend вместо backend. Пересоберите с `VITE_DATA_MODE=remote` (в образе включится `nginx.remote.conf`) **и** поднимите backend: `--profile with-backend` |
|
||||||
| 502 на https://social.deepfishing.ru | `docker ps`, логи `sg_frontend` / `sg_backend` |
|
| 502 на https://social.deepfishing.ru | `docker ps`, логи `sg_frontend` / `sg_backend` |
|
||||||
| Страница открывается, API 404 | В прокси включён `ProxyPass /api` → `:8000` |
|
| Страница открывается, API 404 | Backend не запущен или нет прокси `/api` |
|
||||||
| 401 / не пускает | `USE_JWT_AUTH=true` в `.env.prod`, пересобрать backend |
|
| 401 / не пускает | `USE_JWT_AUTH=true` в `.env.prod`, пересобрать backend |
|
||||||
| Данные не сохраняются между устройствами | `VITE_DATA_MODE=remote`, пересобрать frontend |
|
| Данные не сохраняются между устройствами | `VITE_DATA_MODE=remote`, пересобрать frontend |
|
||||||
| Белый экран | `docker logs sg_frontend`, пересборка с `--build` |
|
| Белый экран | `docker logs sg_frontend`, пересборка с `--build` |
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "${FRONTEND_BIND:-127.0.0.1}:${FRONTEND_PORT:-8080}:80"
|
- "${FRONTEND_BIND:-127.0.0.1}:${FRONTEND_PORT:-8080}:80"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_started
|
||||||
|
required: false
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
profiles: ["with-backend"]
|
profiles: ["with-backend"]
|
||||||
|
|||||||
@@ -16,7 +16,14 @@ RUN npm run build
|
|||||||
|
|
||||||
FROM nginx:1.27-alpine AS runtime
|
FROM nginx:1.27-alpine AS runtime
|
||||||
|
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
ARG VITE_DATA_MODE=local
|
||||||
|
COPY nginx.conf /tmp/nginx.local.conf
|
||||||
|
COPY nginx.remote.conf /tmp/nginx.remote.conf
|
||||||
|
RUN if [ "$VITE_DATA_MODE" = "remote" ]; then \
|
||||||
|
cp /tmp/nginx.remote.conf /etc/nginx/conf.d/default.conf; \
|
||||||
|
else \
|
||||||
|
cp /tmp/nginx.local.conf /etc/nginx/conf.d/default.conf; \
|
||||||
|
fi
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/css application/javascript application/json image/svg+xml;
|
||||||
|
|
||||||
|
# API → Django backend (контейнер backend в docker-compose)
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://backend:8000/api/;
|
||||||
|
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_connect_timeout 10s;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||||
|
expires 7d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
export function formatApiErrorData(data) {
|
export function formatApiErrorData(data) {
|
||||||
if (!data) return ''
|
if (!data) return ''
|
||||||
if (typeof data === 'string' && data.trim()) return data.trim()
|
if (typeof data === 'string' && data.trim()) {
|
||||||
|
const text = data.trim()
|
||||||
|
if (text.startsWith('<') && text.includes('</html>')) {
|
||||||
|
const title = text.match(/<title>([^<]+)<\/title>/i)?.[1]?.trim()
|
||||||
|
if (title) return title
|
||||||
|
return 'Сервер вернул HTML вместо JSON. Проверьте проксирование /api на backend.'
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
if (typeof data?.detail === 'string' && data.detail.trim()) return data.detail.trim()
|
if (typeof data?.detail === 'string' && data.detail.trim()) return data.detail.trim()
|
||||||
if (Array.isArray(data?.non_field_errors) && data.non_field_errors.length) {
|
if (Array.isArray(data?.non_field_errors) && data.non_field_errors.length) {
|
||||||
return data.non_field_errors.join('; ')
|
return data.non_field_errors.join('; ')
|
||||||
|
|||||||
Reference in New Issue
Block a user