docs: update deployment instructions for Auth Portal with Traefik in a single Docker Compose stack
This commit is contained in:
@@ -58,8 +58,8 @@ pnpm dlx shadcn@latest add @reui/auth-13 --yes
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
**Production за Traefik** (удалённый сервер, Compose или `docker run`, HTTPS через Cloudflare DNS challenge):
|
||||
[`docs/deploy-traefik.md`](docs/deploy-traefik.md) · пример [`deploy/docker-compose.traefik.yml`](deploy/docker-compose.traefik.yml)
|
||||
**Production (один Compose: Traefik + auth-portal, HTTPS Cloudflare DNS challenge):**
|
||||
[`docs/deploy-traefik.md`](docs/deploy-traefik.md) · [`deploy/docker-compose.traefik.yml`](deploy/docker-compose.traefik.yml) · [`deploy/env.traefik.example`](deploy/env.traefik.example)
|
||||
|
||||
## Структура
|
||||
|
||||
|
||||
@@ -1,20 +1,61 @@
|
||||
# Auth Portal behind an existing Traefik (production).
|
||||
# Auth Portal + Traefik in one Compose stack (production).
|
||||
# Docs: docs/deploy-traefik.md
|
||||
#
|
||||
# Usage on server:
|
||||
# On server:
|
||||
# mkdir -p /opt/auth-portal/data
|
||||
# cp deploy/docker-compose.traefik.yml /opt/auth-portal/docker-compose.yml
|
||||
# # edit networks.proxy.name if your Traefik network is not "proxy"
|
||||
# docker compose --env-file .env up -d
|
||||
# cp deploy/env.traefik.example /opt/auth-portal/.env # fill secrets
|
||||
# docker login git.shts.su
|
||||
# cd /opt/auth-portal && docker compose pull && docker compose up -d
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:${TRAEFIK_IMAGE_TAG:-v3.7}
|
||||
container_name: auth-portal-traefik
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
ports:
|
||||
- "${TRAEFIK_HTTP_PORT:-80}:80"
|
||||
- "${TRAEFIK_HTTPS_PORT:-443}:443"
|
||||
environment:
|
||||
CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN:?set CF_DNS_API_TOKEN in .env}
|
||||
# Optional if DNS token lacks Zone:Read:
|
||||
# CF_ZONE_API_TOKEN: ${CF_ZONE_API_TOKEN:-}
|
||||
command:
|
||||
- --log.level=${TRAEFIK_LOG_LEVEL:-INFO}
|
||||
- --api.dashboard=false
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.docker.network=auth-portal
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
||||
- --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL:?set LETSENCRYPT_EMAIL in .env}
|
||||
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
|
||||
- --certificatesresolvers.letsencrypt.acme.dnschallenge=true
|
||||
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
|
||||
- --certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=15
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- traefik_letsencrypt:/letsencrypt
|
||||
networks:
|
||||
- auth-portal
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
app:
|
||||
image: git.shts.su/denozord/auth-portal:${AUTH_IMAGE_TAG:-latest}
|
||||
pull_policy: always
|
||||
container_name: auth-portal
|
||||
restart: unless-stopped
|
||||
# Do not publish ports — Traefik reaches the container on the shared network.
|
||||
# ports:
|
||||
# - "8080:8080"
|
||||
depends_on:
|
||||
- traefik
|
||||
# No host ports — only Traefik publishes 80/443.
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
@@ -33,14 +74,14 @@ services:
|
||||
volumes:
|
||||
- ./data:/data
|
||||
networks:
|
||||
- proxy
|
||||
- auth-portal
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.network=proxy
|
||||
- traefik.docker.network=auth-portal
|
||||
- traefik.http.routers.auth-portal.rule=Host(`${AUTH_DOMAIN:-auth.shnt.top}`)
|
||||
- traefik.http.routers.auth-portal.entrypoints=websecure
|
||||
- traefik.http.routers.auth-portal.tls=true
|
||||
- traefik.http.routers.auth-portal.tls.certresolver=${TRAEFIK_CERTRESOLVER:-letsencrypt}
|
||||
- traefik.http.routers.auth-portal.tls.certresolver=letsencrypt
|
||||
- traefik.http.services.auth-portal.loadbalancer.server.port=8080
|
||||
healthcheck:
|
||||
test:
|
||||
@@ -60,8 +101,10 @@ services:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
volumes:
|
||||
traefik_letsencrypt:
|
||||
name: auth_portal_traefik_letsencrypt
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
# Rename if Traefik uses another network (traefik, web, edge, …):
|
||||
name: proxy
|
||||
auth-portal:
|
||||
name: auth-portal
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Production .env for deploy/docker-compose.traefik.yml (auth + Traefik).
|
||||
# Copy to /opt/auth-portal/.env and fill secrets. Do not commit.
|
||||
|
||||
# --- Traefik / Let's Encrypt (Cloudflare DNS-01) ---
|
||||
CF_DNS_API_TOKEN=
|
||||
LETSENCRYPT_EMAIL=admin@shnt.top
|
||||
AUTH_DOMAIN=auth.shnt.top
|
||||
# TRAEFIK_IMAGE_TAG=v3.7
|
||||
# TRAEFIK_HTTP_PORT=80
|
||||
# TRAEFIK_HTTPS_PORT=443
|
||||
# TRAEFIK_LOG_LEVEL=INFO
|
||||
|
||||
# --- Auth Portal ---
|
||||
AUTH_IMAGE_TAG=latest
|
||||
JWT_SECRET=
|
||||
JWT_TTL_HOURS=1
|
||||
REFRESH_TTL_DAYS=14
|
||||
ISSUER=https://auth.shnt.top
|
||||
ADMIN_EMAIL=admin@shnt.top
|
||||
ADMIN_PASSWORD=
|
||||
ADMIN_NAME=Admin
|
||||
RETURN_TO_ALLOWLIST=.shnt.top,https://vps.shnt.top,https://cfdm.shnt.top
|
||||
LOG_LEVEL=info
|
||||
NODE_ENV=production
|
||||
+95
-258
@@ -1,306 +1,154 @@
|
||||
# Развёртывание Auth Portal за Traefik
|
||||
# Развёртывание Auth Portal + Traefik (один Docker Compose)
|
||||
|
||||
Production: один контейнер (API + статика SPA), SQLite в томе, HTTPS через Traefik + Let's Encrypt (**DNS-01 challenge через Cloudflare**).
|
||||
Один стек: **Traefik** (HTTPS, Let's Encrypt DNS-01 через Cloudflare) + **auth-portal** (API + SPA, SQLite).
|
||||
|
||||
Образ: `git.shts.su/denozord/auth-portal` (CI: `.gitea/workflows/docker.yml`).
|
||||
Публичный URL по умолчанию: `https://auth.shnt.top`.
|
||||
Образ приложения: `git.shts.su/denozord/auth-portal`
|
||||
Публичный URL: `https://auth.shnt.top`
|
||||
Compose: [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml)
|
||||
Env-шаблон: [`deploy/env.traefik.example`](../deploy/env.traefik.example)
|
||||
|
||||
Документация:
|
||||
- Traefik Docker / TLS: [Expose Docker](https://doc.traefik.io/traefik/expose/docker/basic/), [ACME](https://doc.traefik.io/traefik/https/acme/)
|
||||
- Cloudflare как DNS provider для Lego/Traefik: env `CF_DNS_API_TOKEN`
|
||||
Документация Traefik: [Expose Docker](https://doc.traefik.io/traefik/expose/docker/basic/), [ACME DNS challenge](https://doc.traefik.io/traefik/https/acme/).
|
||||
|
||||
```
|
||||
Internet → :80/:443 (Traefik) → auth-portal:8080
|
||||
↑
|
||||
Cloudflare DNS TXT (ACME)
|
||||
```
|
||||
|
||||
## Предпосылки
|
||||
|
||||
1. Зона `shnt.top` (или ваш домен) в Cloudflare.
|
||||
2. Docker на сервере; сеть для Traefik (часто `proxy`).
|
||||
3. Доступ к registry: `docker login git.shts.su`.
|
||||
4. Каталоги:
|
||||
1. Зона домена в Cloudflare (например `shnt.top`).
|
||||
2. Docker Engine + Compose plugin на VPS.
|
||||
3. Свободные порты **80** и **443** на хосте (этот стек сам поднимает Traefik).
|
||||
4. `docker login git.shts.su`.
|
||||
|
||||
```bash
|
||||
mkdir -p /opt/traefik /opt/auth-portal/data
|
||||
```
|
||||
> Если на сервере уже крутится другой Traefik на 80/443 — либо остановите его, либо смените `TRAEFIK_HTTP_PORT` / `TRAEFIK_HTTPS_PORT` (и проброс снаружи). Два Traefik на одних портах не запустятся.
|
||||
|
||||
---
|
||||
|
||||
## HTTPS: DNS challenge Cloudflare
|
||||
## 1. Cloudflare: токен и DNS
|
||||
|
||||
HTTP-01 на порту 80 не нужен: Traefik создаёт TXT `_acme-challenge…` в Cloudflare и получает сертификат Let's Encrypt. Удобно, если порт 80 закрыт, домен за CF proxy, или нужен wildcard.
|
||||
### API-токен
|
||||
|
||||
### 1. API-токен Cloudflare
|
||||
|
||||
[Cloudflare Dashboard → My Profile → API Tokens → Create Token](https://dash.cloudflare.com/profile/api-tokens)
|
||||
|
||||
Шаблон **Edit zone DNS** или Custom:
|
||||
[API Tokens → Create Token](https://dash.cloudflare.com/profile/api-tokens) — шаблон **Edit zone DNS** или Custom:
|
||||
|
||||
| Permission | Access |
|
||||
|------------|--------|
|
||||
| Zone → DNS | Edit |
|
||||
| Zone → Zone | Read (желательно) |
|
||||
|
||||
**Zone Resources:** Include → Specific zone → `shnt.top` (минимальный scope).
|
||||
**Zone Resources:** Include → Specific zone → ваша зона.
|
||||
|
||||
Скопируйте токен один раз → это `CF_DNS_API_TOKEN`.
|
||||
Не путать с Global API Key и не класть токен в репозиторий.
|
||||
Токен → `CF_DNS_API_TOKEN` в `.env` стека. Не Global API Key, не в Git.
|
||||
|
||||
### 2. DNS-запись для портала
|
||||
|
||||
В Cloudflare → DNS → Records:
|
||||
### DNS-запись
|
||||
|
||||
| Type | Name | Content | Proxy |
|
||||
|------|------|---------|-------|
|
||||
| `A` (или `AAAA`) | `auth` | публичный IP сервера | **DNS only** (серое облако) |
|
||||
|
||||
Для origin за Traefik на VPS обычно **DNS only**. Orange cloud (proxied) возможен, но тогда SSL/режимы CF настраиваются отдельно; ACME DNS-01 от этого не зависит.
|
||||
|
||||
Проверка:
|
||||
| `A` / `AAAA` | `auth` | IP VPS | **DNS only** (серое облако) |
|
||||
|
||||
```bash
|
||||
dig +short auth.shnt.top A
|
||||
```
|
||||
|
||||
### 3. Traefik с `dnschallenge.provider=cloudflare`
|
||||
|
||||
Если Traefik уже настроен так же — переходите к [разделу Auth Portal](#вариант-a--docker-compose).
|
||||
Если нет — пример `/opt/traefik/docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.7
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN:?set CF_DNS_API_TOKEN}
|
||||
# опционально: CF_ZONE_API_TOKEN с Zone:Read, если DNS-токен без Zone:Read
|
||||
command:
|
||||
- --log.level=INFO
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.docker.network=proxy
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
||||
- --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL:?set email}
|
||||
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
|
||||
- --certificatesresolvers.letsencrypt.acme.dnschallenge=true
|
||||
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
|
||||
- --certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=15
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- traefik_letsencrypt:/letsencrypt
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
volumes:
|
||||
traefik_letsencrypt:
|
||||
name: traefik_letsencrypt
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
```
|
||||
|
||||
Файл `/opt/traefik/.env`:
|
||||
|
||||
```env
|
||||
CF_DNS_API_TOKEN=<токен из шага 1>
|
||||
LETSENCRYPT_EMAIL=admin@shnt.top
|
||||
```
|
||||
|
||||
Запуск Traefik:
|
||||
|
||||
```bash
|
||||
cd /opt/traefik
|
||||
docker network create proxy 2>/dev/null || true
|
||||
docker compose up -d
|
||||
docker compose logs -f --tail=50
|
||||
```
|
||||
|
||||
Имя resolver'а в команде — `letsencrypt`. Его же указывают приложения в label:
|
||||
|
||||
`traefik.http.routers.…tls.certresolver=letsencrypt`
|
||||
|
||||
**Важно:** `CF_DNS_API_TOKEN` задаётся в **окружении контейнера Traefik**, не auth-portal. Том `traefik_letsencrypt` хранит `acme.json` — не удаляйте (`docker compose down -v`) без бэкапа.
|
||||
|
||||
Проверка сети:
|
||||
|
||||
```bash
|
||||
docker network ls
|
||||
docker inspect traefik --format '{{json .NetworkSettings.Networks}}'
|
||||
```
|
||||
Traefik для сертификата создаёт TXT `_acme-challenge.auth…` через Cloudflare API (HTTP-01 на :80 не обязателен).
|
||||
|
||||
---
|
||||
|
||||
## Переменные окружения Auth Portal
|
||||
## 2. Файлы на сервере
|
||||
|
||||
Файл `/opt/auth-portal/.env` (не коммитить):
|
||||
```bash
|
||||
mkdir -p /opt/auth-portal/data
|
||||
cd /opt/auth-portal
|
||||
|
||||
```env
|
||||
# Обязательно: общий секрет с VPS Tracker / CFDM (AUTH_JWT_SECRET)
|
||||
JWT_SECRET=<длинная-случайная-строка>
|
||||
# из репозитория или curl:
|
||||
# deploy/docker-compose.traefik.yml → docker-compose.yml
|
||||
# deploy/env.traefik.example → .env
|
||||
|
||||
ISSUER=https://auth.shnt.top
|
||||
JWT_TTL_HOURS=1
|
||||
REFRESH_TTL_DAYS=14
|
||||
curl -fsSL -o docker-compose.yml \
|
||||
https://git.shts.su/denozord/auth-portal/raw/branch/main/deploy/docker-compose.traefik.yml
|
||||
curl -fsSL -o .env \
|
||||
https://git.shts.su/denozord/auth-portal/raw/branch/main/deploy/env.traefik.example
|
||||
|
||||
# Bootstrap admin — только при пустой БД
|
||||
ADMIN_EMAIL=admin@shnt.top
|
||||
ADMIN_PASSWORD=<сильный-пароль>
|
||||
ADMIN_NAME=Admin
|
||||
|
||||
# Origins приложений (SSO return_to). Доменный суффикс .shnt.top покрывает поддомены.
|
||||
RETURN_TO_ALLOWLIST=.shnt.top,https://vps.shnt.top,https://cfdm.shnt.top
|
||||
|
||||
DATABASE_URL=sqlite:/data/app.db
|
||||
STATIC_DIR=/app/static
|
||||
LOG_LEVEL=info
|
||||
NODE_ENV=production
|
||||
|
||||
# Публичный хост (для compose-labels) + имя ACME resolver Traefik
|
||||
AUTH_DOMAIN=auth.shnt.top
|
||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
||||
nano .env # заполнить секреты
|
||||
```
|
||||
|
||||
### Обязательные переменные в `.env`
|
||||
|
||||
| Переменная | Назначение |
|
||||
|------------|------------|
|
||||
| `JWT_SECRET` | HS256 для access JWT; тот же секрет в приложениях |
|
||||
| `ISSUER` | Должен совпадать с `AUTH_ISSUER` в приложениях |
|
||||
| `RETURN_TO_ALLOWLIST` | Иначе SSO `return_to` отклоняется |
|
||||
| `ADMIN_*` | Первый админ при пустом `/data/app.db` |
|
||||
| `TRAEFIK_CERTRESOLVER` | Имя resolver'а Traefik (`letsencrypt`) |
|
||||
| `CF_DNS_API_TOKEN` | Cloudflare token для ACME DNS-01 (env контейнера **Traefik**) |
|
||||
| `LETSENCRYPT_EMAIL` | Email для Let's Encrypt |
|
||||
| `JWT_SECRET` | HS256; тот же секрет в VPS Tracker / CFDM (`AUTH_JWT_SECRET`) |
|
||||
| `ADMIN_PASSWORD` | Пароль bootstrap-админа (только при пустой БД) |
|
||||
| `AUTH_DOMAIN` | Хост в Traefik rule, по умолчанию `auth.shnt.top` |
|
||||
| `ISSUER` | `https://auth.shnt.top` — совпадает с `AUTH_ISSUER` приложений |
|
||||
| `RETURN_TO_ALLOWLIST` | Origins SSO (например `.shnt.top`) |
|
||||
|
||||
В production cookie refresh ставится с флагом `Secure` — нужен HTTPS (Traefik).
|
||||
Опционально: `AUTH_IMAGE_TAG`, `TRAEFIK_IMAGE_TAG`, `TRAEFIK_HTTP_PORT`, `TRAEFIK_HTTPS_PORT`.
|
||||
|
||||
В production cookie refresh с флагом `Secure` — нужен HTTPS.
|
||||
|
||||
---
|
||||
|
||||
## Вариант A — Docker Compose
|
||||
|
||||
Готовый файл в репозитории: [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml).
|
||||
|
||||
На сервере:
|
||||
## 3. Запуск (один compose)
|
||||
|
||||
```bash
|
||||
cd /opt/auth-portal
|
||||
# скопируйте deploy/docker-compose.traefik.yml → docker-compose.yml
|
||||
# или:
|
||||
curl -fsSL -o docker-compose.yml \
|
||||
https://git.shts.su/denozord/auth-portal/raw/branch/main/deploy/docker-compose.traefik.yml
|
||||
|
||||
# поправьте networks.proxy.name под вашу сеть Traefik
|
||||
nano docker-compose.yml
|
||||
nano .env
|
||||
|
||||
docker login git.shts.su
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
docker compose ps
|
||||
docker compose logs -f --tail=100
|
||||
```
|
||||
|
||||
После старта Traefik запросит сертификат (DNS TXT в Cloudflare). Первая выдача может занять 30–90 с.
|
||||
Сервисы:
|
||||
|
||||
Проверка:
|
||||
| Service | Контейнер | Роль |
|
||||
|---------|-----------|------|
|
||||
| `traefik` | `auth-portal-traefik` | :80 → HTTPS, ACME, роутинг |
|
||||
| `app` | `auth-portal` | приложение на внутренней сети `auth-portal`, порт 8080 |
|
||||
|
||||
Первая выдача сертификата обычно 30–90 с.
|
||||
|
||||
### Проверка
|
||||
|
||||
```bash
|
||||
curl -fsS https://auth.shnt.top/health
|
||||
echo | openssl s_client -connect auth.shnt.top:443 -servername auth.shnt.top 2>/dev/null | openssl x509 -noout -issuer -dates -subject
|
||||
echo | openssl s_client -connect auth.shnt.top:443 -servername auth.shnt.top 2>/dev/null \
|
||||
| openssl x509 -noout -issuer -dates -subject
|
||||
|
||||
docker compose logs traefik 2>&1 | grep -iE 'acme|certificate|cloudflare|error'
|
||||
```
|
||||
|
||||
Обновление образа:
|
||||
### Обновление / остановка
|
||||
|
||||
```bash
|
||||
cd /opt/auth-portal
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Остановка:
|
||||
|
||||
```bash
|
||||
# остановка (тома и ./data сохраняются)
|
||||
docker compose down
|
||||
# данные в ./data сохраняются (не используйте -v без бэкапа)
|
||||
# НЕ делайте down -v без бэкапа — сотрёт ACME (auth_portal_traefik_letsencrypt)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Вариант B — Docker CLI
|
||||
## Что внутри compose
|
||||
|
||||
Подставьте имя сети Traefik вместо `proxy`, если нужно.
|
||||
- Сеть Docker **`auth-portal`** (внутренняя, создаётся стеком).
|
||||
- Том **`auth_portal_traefik_letsencrypt`** → `/letsencrypt/acme.json`.
|
||||
- Том хоста **`./data`** → SQLite портала.
|
||||
- Labels на `app`: `Host(AUTH_DOMAIN)`, `entrypoints=websecure`, `certresolver=letsencrypt`, backend port `8080`.
|
||||
- `CF_DNS_API_TOKEN` только у сервиса `traefik`.
|
||||
|
||||
```bash
|
||||
cd /opt/auth-portal
|
||||
set -a && source .env && set +a
|
||||
|
||||
docker pull git.shts.su/denozord/auth-portal:latest
|
||||
|
||||
docker rm -f auth-portal 2>/dev/null || true
|
||||
|
||||
DOMAIN="${AUTH_DOMAIN:-auth.shnt.top}"
|
||||
NETWORK=proxy # имя сети Traefik
|
||||
RESOLVER="${TRAEFIK_CERTRESOLVER:-letsencrypt}"
|
||||
|
||||
docker run -d \
|
||||
--name auth-portal \
|
||||
--restart unless-stopped \
|
||||
--network "$NETWORK" \
|
||||
--env-file /opt/auth-portal/.env \
|
||||
-v /opt/auth-portal/data:/data \
|
||||
-l traefik.enable=true \
|
||||
-l "traefik.docker.network=${NETWORK}" \
|
||||
-l "traefik.http.routers.auth-portal.rule=Host(\`${DOMAIN}\`)" \
|
||||
-l traefik.http.routers.auth-portal.entrypoints=websecure \
|
||||
-l traefik.http.routers.auth-portal.tls=true \
|
||||
-l "traefik.http.routers.auth-portal.tls.certresolver=${RESOLVER}" \
|
||||
-l traefik.http.services.auth-portal.loadbalancer.server.port=8080 \
|
||||
git.shts.su/denozord/auth-portal:latest
|
||||
```
|
||||
|
||||
Порты хоста (`-p 8080:8080`) **не публикуйте**, если трафик только через Traefik.
|
||||
|
||||
Обновление:
|
||||
|
||||
```bash
|
||||
docker pull git.shts.su/denozord/auth-portal:latest
|
||||
docker stop auth-portal
|
||||
docker rm auth-portal
|
||||
# повторите docker run …
|
||||
```
|
||||
|
||||
Логи / health:
|
||||
|
||||
```bash
|
||||
docker logs -f --tail=100 auth-portal
|
||||
docker logs -f --tail=100 traefik
|
||||
docker inspect --format='{{.State.Health.Status}}' auth-portal
|
||||
curl -fsS https://auth.shnt.top/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Labels Traefik (справка)
|
||||
|
||||
| Label | Значение |
|
||||
|-------|----------|
|
||||
| `traefik.enable` | `true` |
|
||||
| `traefik.docker.network` | имя общей сети с Traefik |
|
||||
| `…routers.auth-portal.rule` | `Host(\`auth.shnt.top\`)` |
|
||||
| `…entrypoints` | `websecure` |
|
||||
| `…tls` / `…tls.certresolver` | `true` / `letsencrypt` (имя из ACME resolver) |
|
||||
| `…loadbalancer.server.port` | `8080` (порт внутри контейнера) |
|
||||
|
||||
Если resolver называется иначе (`le`, `cf`), замените `certresolver=…` и `TRAEFIK_CERTRESOLVER`.
|
||||
Полный файл: [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml).
|
||||
|
||||
---
|
||||
|
||||
## Связка с приложениями
|
||||
|
||||
После деплоя портала в VPS Tracker / CFDM:
|
||||
|
||||
```env
|
||||
AUTH_REQUIRED=true
|
||||
AUTH_JWT_SECRET=<тот же JWT_SECRET>
|
||||
@@ -315,28 +163,23 @@ VITE_AUTH_ENABLED=true
|
||||
VITE_AUTH_PORTAL_URL=https://auth.shnt.top
|
||||
```
|
||||
|
||||
Интеграции: [integrate-vps-tracker.md](integrate-vps-tracker.md), [integrate-cfdm.md](integrate-cfdm.md).
|
||||
|
||||
Logout SSO: приложения редиректят на `https://auth.shnt.top/logout` (не на `/?return_to=…`).
|
||||
См. [integrate-vps-tracker.md](integrate-vps-tracker.md), [integrate-cfdm.md](integrate-cfdm.md).
|
||||
Logout SSO: `https://auth.shnt.top/logout`.
|
||||
|
||||
---
|
||||
|
||||
## Бэкап и откат
|
||||
## Бэкап
|
||||
|
||||
```bash
|
||||
# бэкап SQLite
|
||||
# SQLite
|
||||
cp /opt/auth-portal/data/app.db /opt/auth-portal/data/app.db.bak-$(date +%F)
|
||||
|
||||
# бэкап ACME (сертификаты Traefik)
|
||||
docker run --rm -v traefik_letsencrypt:/data -v "$PWD:/backup" alpine \
|
||||
# ACME
|
||||
docker run --rm -v auth_portal_traefik_letsencrypt:/data -v "$PWD:/backup" alpine \
|
||||
tar czf /backup/traefik-acme-$(date +%F).tgz -C /data .
|
||||
|
||||
# откат на тег релиза
|
||||
docker pull git.shts.su/denozord/auth-portal:vX.Y.Z
|
||||
# в compose: image: …:vX.Y.Z → up -d
|
||||
```
|
||||
|
||||
Не удаляйте том/каталог `data` и том `traefik_letsencrypt` без необходимости.
|
||||
Откат образа: в `.env` `AUTH_IMAGE_TAG=vX.Y.Z` → `docker compose pull && docker compose up -d`.
|
||||
|
||||
---
|
||||
|
||||
@@ -344,27 +187,21 @@ docker pull git.shts.su/denozord/auth-portal:vX.Y.Z
|
||||
|
||||
| Симптом | Что проверить |
|
||||
|---------|----------------|
|
||||
| 404 / Gateway Timeout | Контейнер в той же сети, что Traefik; `traefik.docker.network`; label `loadbalancer.server.port=8080` |
|
||||
| ACME / TLS не выдаётся | `CF_DNS_API_TOKEN` в env **Traefik**; права `Zone:DNS:Edit`; `dnschallenge.provider=cloudflare`; логи `docker logs traefik` на `error` / `acme` |
|
||||
| `invalid credentials` / Cloudflare API | Токен не Global Key; зона в scope токена; нет лишних пробелов/кавычек в `.env` |
|
||||
| TXT не появляется | Токен без Edit на зоне; неверная зона (другой аккаунт CF) |
|
||||
| Сертификат есть, сайт не открывается | `A` на IP сервера; firewall 443; record не указывает на старый IP |
|
||||
| Login OK, SSO в app падает | `JWT_SECRET` / `ISSUER` совпадают; app в JWT `apps` |
|
||||
| `return_to` rejected | origin приложения в `RETURN_TO_ALLOWLIST` |
|
||||
| Cookie не держится | HTTPS; `NODE_ENV=production` → `Secure` |
|
||||
| «Выйти» возвращает в app | Нужен образ портала с `/logout` и клиенты с `redirectToPortalLogout` |
|
||||
| Пустой UI / 404 статики | В образе `STATIC_DIR=/app/static` (зашито в Dockerfile) |
|
||||
|
||||
Полезные логи ACME:
|
||||
| `Bind for 0.0.0.0:80/443 failed` | Другой Traefik/nginx занимает порты |
|
||||
| ACME / нет HTTPS | `CF_DNS_API_TOKEN`, права Zone:DNS:Edit, логи `docker compose logs traefik` |
|
||||
| `invalid credentials` | Не Global Key; зона в scope токена; пробелы в `.env` |
|
||||
| Gateway Timeout / 404 | `docker compose ps`; labels; сеть `auth-portal` |
|
||||
| Login OK, SSO в app падает | `JWT_SECRET` / `ISSUER` |
|
||||
| `return_to` rejected | `RETURN_TO_ALLOWLIST` |
|
||||
| Cookie не держится | HTTPS; `NODE_ENV=production` |
|
||||
|
||||
```bash
|
||||
docker logs traefik 2>&1 | grep -iE 'acme|certificate|cloudflare|error'
|
||||
docker compose logs traefik 2>&1 | grep -iE 'acme|certificate|cloudflare|error'
|
||||
docker compose logs app --tail=50
|
||||
```
|
||||
|
||||
Локальный smoke без Traefik (порт наружу):
|
||||
---
|
||||
|
||||
```bash
|
||||
docker run --rm -p 8080:8080 --env-file .env -v "$PWD/data:/data" \
|
||||
git.shts.su/denozord/auth-portal:latest
|
||||
curl -fsS http://127.0.0.1:8080/health
|
||||
```
|
||||
## Альтернатива: Docker CLI (без compose)
|
||||
|
||||
Если нужен ручной запуск — создайте сеть и два контейнера с теми же env/labels, что в compose. Предпочтительный путь — **один `docker compose up -d`** выше.
|
||||
|
||||
Reference in New Issue
Block a user