# Docker Images Образ собирается из [`Dockerfile.fast`](Dockerfile.fast) (многостадийная сборка: Vite frontend + `npm ci` backend, рантайм **distroless** Node 20). В Gitea Actions используется тот же файл (см. [`.gitea/workflows/docker-publish.yml`](.gitea/workflows/docker-publish.yml)). ## Теги в реестре `git.shts.su` Замените `[repository]` на путь репозитория в Gitea (например `denozord/router-lists-ui`). | Ветка (триггер push) | Образы в реестре | |----------------------|-------------------| | **main** (и v4, v3 и т.д. из workflow) | `git.shts.su/[repository]:latest` и `git.shts.su/[repository]:<полный-sha>` | | **v5** | **только** `git.shts.su/[repository]:v5` и `git.shts.su/[repository]:v5-<полный-sha>` — тег **`latest` с ветки v5 не публикуется** | | **tabler** | см. отдельный workflow `docker-publish-tabler.yml`, если включён | Пин на коммит: используйте тег с суффиксом `-`; «плавающий» тег ветки (`:v5` или `:latest`) всегда указывает на последнюю успешную сборку. ## Запуск (production) ### Общие рекомендации 1. **Данные на хосте** — смонтируйте том для SQLite и каталог бэкапов MikroTik, иначе при удалении контейнера пропадут БД и `.rsc`. 2. **Переменные** — минимум `ENCRYPTION_KEY`; для справочников и UI — `EVOBGP_API_URL` и `EVOBGP_API_TOKEN`; при необходимости `CORS_ORIGINS`, `PORT`. 3. **Рантайм distroless** — внутри образа **нет** `/bin/bash` и пакетного менеджера; отладка через `docker logs` / метрики. Интерактивная оболочка недоступна. ### Основная линия (`:latest`) ```bash docker run -d \ --name router-lists-ui \ -p 3001:3001 \ -e ENCRYPTION_KEY=<64-hex> \ -e EVOBGP_API_URL=https://evobgp.example/v1 \ -e EVOBGP_API_TOKEN= \ -e SQLITE_PATH=/data/router-lists.db \ -e MIKROTIK_BACKUP_DIR=/data/backups/mikrotik \ -v router-lists-data:/data \ git.shts.su/[repository]:latest ``` Каталог `/data` на томе должен быть доступен пользователю **nonroot** (образ distroless); при ошибках прав создайте том заранее или используйте bind-mount с `chown` под UID образа (обычно `65532` у nonroot). ### Ветка v5 (только теги `v5` / `v5-`) ```bash docker pull git.shts.su/[repository]:v5 docker run -d \ --name router-lists-ui-v5 \ -p 3002:3001 \ -e ENCRYPTION_KEY=<64-hex> \ -e EVOBGP_API_URL=https://evobgp.example/v1 \ -e EVOBGP_API_TOKEN= \ -e SQLITE_PATH=/data/router-lists.db \ -e MIKROTIK_BACKUP_DIR=/data/backups/mikrotik \ -v router-lists-v5-data:/data \ git.shts.su/[repository]:v5 ``` Чтобы зафиксировать конкретный коммит: ```bash docker run -d ... git.shts.su/[repository]:v5-1c6e6ab... ``` ### Tabler-ветка (если публикуется отдельным workflow) ```bash docker run -d \ --name router-lists-ui-tabler \ -p 3003:3001 \ --env-file ./backend/.env \ -v router-lists-tabler-data:/data \ git.shts.su/[repository]:tabler ``` ## Обновление образа ### latest ```bash docker pull git.shts.su/[repository]:latest docker stop router-lists-ui && docker rm router-lists-ui # затем снова docker run ... как выше ``` ### v5 ```bash docker pull git.shts.su/[repository]:v5 docker stop router-lists-ui-v5 && docker rm router-lists-ui-v5 docker run -d ... git.shts.su/[repository]:v5 ``` ## Docker Compose Пример **одной** службы на `latest` с томом данных (подставьте свой `image:` и при необходимости `env_file`): ```yaml services: router-lists-ui: image: git.shts.su/[repository]:latest container_name: router-lists-ui ports: - "3001:3001" restart: unless-stopped environment: NODE_ENV: production ENCRYPTION_KEY: ${ENCRYPTION_KEY} EVOBGP_API_URL: ${EVOBGP_API_URL} EVOBGP_API_TOKEN: ${EVOBGP_API_TOKEN} SQLITE_PATH: /data/router-lists.db MIKROTIK_BACKUP_DIR: /data/backups/mikrotik volumes: - router-lists-data:/data volumes: router-lists-data: ``` Отдельный сервис для **v5** — тот же фрагмент, но `image: git.shts.su/[repository]:v5`, другой `container_name` и при желании отдельный volume. ## Переменные окружения (контейнер) | Переменная | Описание | Обязательная | |------------|----------|----------------| | `ENCRYPTION_KEY` | AES-256 для IPSec и паролей MikroTik (64 hex) | **Да** | | `EVOBGP_API_URL` | Базовый URL API EvoBGP (серверный прокси `/api/evobgp` и бэкенд-справочники) | По сценарию | | `EVOBGP_API_TOKEN` | Bearer к EvoBGP | По сценарию | | `SQLITE_PATH` | Путь к файлу SQLite внутри контейнера (персистентный том) | Рекомендуется явно задать, напр. `/data/router-lists.db` | | `MIKROTIK_BACKUP_DIR` | Каталог бэкапов `.rsc` на диске | Рекомендуется с томом, напр. `/data/backups/mikrotik` | | `PORT` | Порт HTTP внутри контейнера | Нет (по умолчанию `3001`) | | `CORS_ORIGINS` | Список Origin через запятую | Нет | | `LOG_LEVEL` | Уровень логов pino | Нет | `ENCRYPTION_KEY` должен быть **один и тот же** между перезапусками, иначе не расшифровать уже сохранённые секреты. ### EvoBGP и reverse proxy (nginx) Запросы UI идут на тот же origin: `/api/evobgp/…` обрабатывает **Node** внутри образа. Если перед приложением стоит **nginx**, нужно проксировать префикс `/api` на Node **без потери** сегмента `/api` в URI. **Рекомендуемый вариант** (путь `/api/…` сохраняется): ```nginx location /api/ { proxy_pass http://127.0.0.1:3001/api/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } ``` **Типичная ошибка**: `proxy_pass http://127.0.0.1:3001/;` — nginx срезает `/api`. В коде есть запасной маршрут на `/evobgp`, но лучше исправить nginx. **Проверка**: `GET http://<хост>:<порт>/api/evobgp` → JSON вида `{"ok":true,"proxy":true,"evobgpConfigured":true}`. ## Генерация `ENCRYPTION_KEY` ```bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` ## Мониторинг ```bash docker logs -f router-lists-ui docker stats router-lists-ui ``` ## Локальная сборка (как в CI) ```bash docker build -f Dockerfile.fast -t router-lists-ui:local . docker run --rm -p 3001:3001 --env-file ./backend/.env -v $(pwd)/data:/data \ -e SQLITE_PATH=/data/router-lists.db \ -e MIKROTIK_BACKUP_DIR=/data/backups/mikrotik \ router-lists-ui:local ``` (PowerShell на Windows: вместо `$(pwd)` используйте путь к каталогу данных.) ## CI/CD (Gitea Actions) - [`.gitea/workflows/docker-publish.yml`](.gitea/workflows/docker-publish.yml) — пуш в перечисленные ветки (включая **v5**): сборка и push в `git.shts.su`. - Для **v5** в реестр уходят **только** теги `v5` и `v5-` (без `latest`). - Остальные ветки из списка workflow — `latest` и тег по SHA. Шаг **Trigger container update webhook** (если задан `secrets.portainer_webhook`) выполняется после сборки; при ошибке шаг не валит job (`continue-on-error: true`). ## Безопасность и сеть ```bash docker network create router-lists-net docker run -d --network router-lists-net ... git.shts.su/[repository]:latest ``` Ограничение ресурсов: ```bash docker run -d --memory=512m --cpus=0.5 ... git.shts.su/[repository]:latest ``` ## Устранение неполадок ```bash docker ps -a --filter name=router-lists-ui docker restart router-lists-ui docker image prune -f ``` --- **Замените `[repository]`** на актуальный путь репозитория в Gitea (например `denozord/router-lists-ui`).