Files
docs/architecture/deployment.md
T
ksv741 6fd39cd467
Docs security / security (pull_request) Successful in 14s
Docs verify / verify (pull_request) Successful in 11s
Conventional commits / commits (pull_request) Successful in 11s
Docs security / security (push) Successful in 16s
Docs verify / verify (push) Successful in 11s
docs: use shared Nexus account #18
2026-09-17 20:53:05 +03:00

18 KiB
Raw Blame History

Контейнерная поставка

Runbook для одного Docker host, одного backend и краткого простоя. Топология и ADR 015 объясняют границы. Команды выполняются из checkout WealthLedger/main, содержащего compose.yaml. Development дополнительно требует checkout frontend/ и backend/.

Предпосылки

  • Docker Engine с Compose plugin не ниже 2.24.4 (!override в development), доступ к registry и достаточно свободного диска для non-root UID 1000. Локально проверены Engine 29.8.0, Compose 5.5.1, Linux arm64. Другую архитектуру проверяют отдельно; multi-platform images не обещаны.
  • Nexus Docker hosted repository docker-hosted, доступный через https://nexus.ksv741.keenetic.pro/v2/. Docker Bearer Token Realm включён; reverse proxy передаёт исходные host и HTTPS scheme. Адрес token realm в WWW-Authenticate также должен начинаться с https://. Deployment policy запрещает redeploy существующих release tags. Runner label — ubuntu-latest, runner должен иметь Docker CLI, buildx и daemon. Наличие label само по себе не доказывает Docker capability.
  • В Actions каждого component repo нужны NEXUS_USERNAME и NEXUS_PASSWORD пользователя wealthledger-ci. Эта же учётная запись используется сервером для загрузки образов. Для публикации нужны browse/read/add/edit на docker-hosted; фактически выданный набор прав настраивается в Nexus. Built-in workflow token не заменяет эту настройку.
  • На сервере Caddy уже завершает TLS и передаёт весь домен на 127.0.0.1:8080 (или выбранный frontend port). Отдельного /api upstream нет. DNS, TLS, firewall и provisioning Caddy выполняются вне этого runbook.

Реальная публикация и server pull-only login требуют отдельной проверки на вашей инфраструктуре. Локальные images не доказывают registry permissions.

Development

Из workspace с исходниками обоих компонентов:

docker version
docker compose version
docker compose --env-file .env.development.example \
  -f compose.yaml -f compose.development.yaml config --quiet
docker compose --env-file .env.development.example \
  -f compose.yaml -f compose.development.yaml up -d --build --wait --wait-timeout 180

Откройте http://localhost:5173. Vite отдаёт HMR и проксирует /api/ без rewrite; Nest работает в watch mode. Монтируются только source/public, node_modules и generated files остаются в container filesystem. .env с хоста не копируется. После изменения manifest, lockfile, tsconfig или Vite config пересоберите соответствующий image. up --build обновляет Linux dependencies без stale named volume для node_modules.

Для другого порта скопируйте .env.development.example в игнорируемый .env.development и согласованно поменяйте FRONTEND_PORT/FRONTEND_ORIGIN. Пример содержит только вымышленные local secrets; server secrets сюда не входят. Поля image/data directory нужны для интерполяции base Compose, но полностью заменяются development override. В итоговом config нет production data path.

Project wealthledger-dev хранит SQLite в development-data. Обычные restart и down сохраняют его. Не применяйте down -v к данным, которые хотите сохранить. Не используйте production project name для development.

Публикация компонентных images

Штатный release process каждого repo создаёт защищённый vX.Y.Z после проверенного release commit. image.yml проверяет tag против package/lock version, затем выполняет npm ci, npm run verify; backend дополнительно выполняет npm run test:e2e. У каждого image OCI version/revision labels.

Images публикуются как nexus.ksv741.keenetic.pro/wealthledger/frontend:X.Y.Z и .../backend:X.Y.Z. latest не создаётся. Workflow отказывается перезаписывать существующие release tags; защита release tags и запрет параллельных publishers обязательны. Если одна публикация уже состоялась, не удаляйте tag для повторного запуска: проверьте опубликованный image и завершите проверку существующего artifact отдельно.

Выберите опубликованные version tags, выполните pull и сравните OCI revision с release commit. Выполните smoke с disposable data/config до первого server rollout. В production env хранится пара полных image references, например nexus.ksv741.keenetic.pro/wealthledger/frontend:0.2.0. Сохраните versions/revisions в deployment record. Release tags не должны переназначаться другим images.

Перенос существующих images из Gitea

До переключения CI и server references проверьте HTTPS token realm и настройте пользователя wealthledger-ci. В Actions добавьте NEXUS_USERNAME/NEXUS_PASSWORD; прежние REGISTRY_USERNAME/REGISTRY_TOKEN сохраните для отката старого workflow. Пароли вводятся интерактивно или через --password-stdin, не через аргументы.

Скопируйте каждый опубликованный release без пересборки. Например, для 0.2.0 с установленным Skopeo и доступом чтения Gitea/записи Nexus:

skopeo login git.personal.ksv741.ru
skopeo login nexus.ksv741.keenetic.pro
for component in frontend backend; do
  skopeo copy --all --preserve-digests \
    "docker://git.personal.ksv741.ru/wealthledger/$component:0.2.0" \
    "docker://nexus.ksv741.keenetic.pro/wealthledger/$component:0.2.0" || exit 1
done

--all переносит весь image index, включая платформы и вложенные attestation manifests; --preserve-digests прерывает перенос при изменении digest. До копирования проверьте наличие destination tag: если он уже существует, сравните исходный и целевой index digest и вложенные manifests. Совпадающий artifact повторно не публикуйте; несовпадение требует расследования, не удаления или перезаписи tag. Частичный перенос не считается завершённым.

После копирования сравните digests через skopeo inspect --raw и SHA-256, выполните docker pull обоих images пользователем wealthledger-ci на сервере приложения, проверьте OCI revision и container smoke на disposable data. Только после этого меняйте production image references и запускайте обычную последовательность обновления ниже. Старые Gitea packages и references сохраните: откат registry возвращает прежние адреса тех же artifacts, не восстанавливает SQLite и не меняет encryption keys. Не переносите Git release tags и не запускайте заново старый tag workflow для копирования существующего image.

Первая установка

Создайте restricted environment file и data directory:

sudo install -d -m 0700 /srv/wealthledger
sudo install -d -m 0700 -o 1000 -g 1000 /srv/wealthledger/data
sudo install -m 0600 .env.compose.example /srv/wealthledger/production.env

Дальнейшие команды выполняет оператор, имеющий доступ к environment file, backup directory и Docker. Заполните env локальным защищённым редактором: полные ссылки FRONTEND_IMAGE и BACKEND_IMAGE с опубликованными version tags, публичный HTTPS origin и абсолютный data path. Версии в примере — образец формата; перед запуском выберите реально опубликованные releases. Пустые required значения блокируют config; некорректные secrets блокируют backend. Не выводите полный rendered config с настоящими secrets в журналы.

Для новой базы создайте JWT secret длиной хотя бы 32 символа и отдельный 32-byte encryption key в base64 (openssl rand -hex 32 и openssl rand -base64 32 в защищённом терминале). Сохраните ключ отдельно от data backup. Для существующей установки перенесите прежние JWT secret и encryption key, а согласованную копию всего data directory — при остановленном прежнем backend. Файл должен называться wealthledger.db; если переименовываете, согласуйте также WAL/SHM companion names до старта. Не запускайте старый и новый backend на одной базе одновременно.

Войдите в registry пользователем wealthledger-ci без помещения пароля в командную строку:

docker login nexus.ksv741.keenetic.pro --username wealthledger-ci
export COMPOSE_FILE=compose.yaml
export COMPOSE_PROJECT_NAME=wealthledger
export COMPOSE_ENV_FILES=/srv/wealthledger/production.env
docker compose config --quiet
docker compose pull frontend backend migration
docker compose up -d --wait --wait-timeout 120
docker compose ps -a

Migration и backend используют один image и один data mount. Migration должна завершиться с кодом 0; иначе backend не запускается. Readiness принимает 401 от существующего /api/v1/auth/me без credentials. Frontend становится healthy после backend. Backend host port отсутствует. Runtime non-root; root filesystem read-only, nginx temp files находятся в tmpfs.

Проверьте SPA deep link, регистрацию/вход, refresh, существующие карты, чтение старого зашифрованного подключения и один финансовый запрос. Production refresh-cookie имеет Secure flag: проверяйте через HTTPS Caddy; loopback HTTP curl без browser cookie jar не доказывает browser session. При локальном smoke используйте disposable credentials и явную передачу cookie. Направляйте Caddy на новый deployment только после успешной проверки. Прежний runtime удаляйте только после проверки новой установки.

Backup при остановленном backend

До backend update проверьте совместимость новой версии с текущим frontend и схемой, а обратной версии — с новой схемой. При неизвестной совместимости не обещайте image rollback. В выбранном rollout отсутствуют параллельные API.

Ниже data path соответствует примеру env; при другом path замените его. Сохраните текущую пару image references/revisions и отдельно — ключи.

docker compose stop backend
docker compose ps -a backend
install -d -m 0700 /srv/wealthledger/backups
BACKUP_DIRECTORY="/srv/wealthledger/backups/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -m 0700 "$BACKUP_DIRECTORY"
cp -a /srv/wealthledger/data "$BACKUP_DIRECTORY/data"

Перед копированием убедитесь, что backend остановлен, migration не выполняется и никто другой не пишет в каталог. Успех cp обязателен перед продолжением. Не копируйте отдельный .db из работающего приложения. Проверяйте backup на отдельной копии: migration status, PRAGMA integrity_check, auth, карты и расшифровка тестового подключения исходным ключом. Backup без проверяемого ключа недостаточен для восстановления подключения.

Независимое обновление frontend

После проверки API compatibility замените только FRONTEND_IMAGE в env:

docker compose pull frontend
docker compose up -d --no-deps --wait --wait-timeout 120 frontend

Backend container и SQLite не пересоздаются. Входная точка кратко недоступна. Проверьте deep link, login/refresh и новую OCI revision. Старым browser tabs может понадобиться reload: старые hashed chunks не сохраняются между images.

Независимое обновление backend

Выполните stopped-backend backup выше. Замените только BACKEND_IMAGE в env. Последовательность не продолжается после failed migration:

docker compose pull backend migration
docker compose up --no-deps --force-recreate --abort-on-container-exit \
  --exit-code-from migration migration &&
docker compose up -d --no-deps --wait --wait-timeout 120 backend

Frontend не пересоздаётся; nginx повторно разрешает backend DNS. Во время перерыва возможен 502. При failure оставьте backend остановленным, сохраните диагностику migration и выберите совместимый image либо отдельный restore. Не используйте migrate reset или автоматический resolve как recovery. Проверьте migration status, health, login/refresh, карты и расшифровку подключения. После успешного smoke запишите фактически установленную пару image references.

Откат image

Для frontend верните предыдущий совместимый version tag и повторите frontend update. Для backend сначала остановите процесс и подтвердите совместимость старого image с текущей схемой. Верните предыдущий image reference и запустите только backend:

docker compose pull backend
docker compose up -d --no-deps --wait --wait-timeout 120 backend

Данные и encryption key не заменяются. Migration service здесь намеренно не запускается: старые migrations не являются down-migrations. Прочитайте новые записи, сделанные после backup, и проверьте auth/cards/connection. При несовместимости оставьте приложение остановленным до решения о recovery.

Explicit restore

Это отдельное решение оператора, не часть image rollback. Оператор выбирает backup timestamp и явно принимает потерю всех изменений после него, включая сессии, карты и подключения. Сначала воспроизведите restore на isolated project с отдельным data path/port и совместимым image; не используйте production data как fixture. Убедитесь в наличии соответствующего encryption key.

После отдельного решения остановите backend и убедитесь, что migration не работает. Сохраните нынешний каталог под новым именем, ничего не удаляя:

docker compose stop backend
mv /srv/wealthledger/data /srv/wealthledger/data-before-restore-TIMESTAMP
cp -a /srv/wealthledger/backups/CHOSEN_TIMESTAMP/data /srv/wealthledger/data

Проверьте owner 1000:1000, permissions, выбранный compatible image и исходный ключ. Запустите backend командой rollback с --no-deps, затем выполните smoke. Сверьте наличие записей до backup и отсутствие известных записей потерянного интервала. Сохраните прежний каталог для расследования. При необходимости новой migration сначала выполните обычную backup/update последовательность.