@@ -1,19 +1,21 @@
# Контейнерная поставка
# Контейнерная поставка
Runbook для одного Docker host, одного backend и краткого простоя.
Поставка для одного Docker host, одного backend и краткого простоя.
[Топология ](containers.md#контейнерная-поставка ) и
[Топология ](containers.md#контейнерная-поставка ) и
[ADR 015 ](../adr/015-single-server-sqlite-deployment.md ) объясняют границы.
[ADR 015 ](../adr/015-single-server-sqlite-deployment.md ) объясняют границы.
Команды выполняются из checkout `WealthLedger/main` , содержащего `compose.yaml` .
Production-комплект находится в отдельном [ репозитории deploy ]( https://git.personal.ksv741.ru/WealthLedger/deploy ) .
Development дополнительно требует checkout `frontend/` и `backend/` .
Его README — единственная пошаговая инструкция установки, обновления и recovery;
[ADR 016 ](../adr/016-versioned-deployment-bundle.md ) объясняет выбор релиза комплекта.
Development-команды ниже выполняются из workspace с checkout `frontend/` и `backend/` .
## Предпосылки
## Предпосылки
- Docker Engine с Compose plugin **не ниже 2.24.4 ** (`!override` в development),
- Docker Engine с Compose plugin **не ниже 2.24.4 ** (`!override` в development),
доступ к registry и достаточно свободного диска для non-root UID 1000.
доступ к registry и достаточно свободного диска для non-root UID 1000.
Локально проверены Engine 29.8.0, Compose 5.5.1, Linux ar m64.
Production images `0.2.3` опубликованы для Linux amd 64.
Другую архитектуру проверяют отдельно; multi-platform images не обещаны.
Другую архитектуру проверяют отдельно; multi-platform images не обещаны.
- Nexus Docker hosted repository `docker-hosted` , доступный через
- Nexus Docker hosted repository `docker-hosted` , доступный через
`https://nexus.ksv741.keenetic.pro/v2 /` . Docker Bearer Token Realm включён;
`nexus.personal.ksv741.ru/docker-hosted /` . Docker Bearer Token Realm включён;
reverse proxy передаёт исходные host и HTTPS scheme. Адрес token realm
reverse proxy передаёт исходные host и HTTPS scheme. Адрес token realm
в `WWW-Authenticate` также должен начинаться с `https://` .
в `WWW-Authenticate` также должен начинаться с `https://` .
Deployment policy запрещает redeploy существующих release tags.
Deployment policy запрещает redeploy существующих release tags.
@@ -29,8 +31,8 @@ Development дополнительно требует checkout `frontend/` и `b
`127.0.0.1:8080` (или выбранный frontend port). Отдельного `/api` upstream нет.
`127.0.0.1:8080` (или выбранный frontend port). Отдельного `/api` upstream нет.
DNS, TLS, firewall и provisioning Caddy выполняются вне этого runbook.
DNS, TLS, firewall и provisioning Caddy выполняются вне этого runbook.
Реальная публикация и server pull-only login требуют отдельной про верки
Серверный login и доступ к images проверяются на целевом сер вере .
на вашей инфраструктуре. Локальные images не доказывают registry permissions.
Локальный smoke не доказывает серверные registry permissions.
## Development
## Development
@@ -69,7 +71,7 @@ Project `wealthledger-dev` хранит SQLite в `development-data`. Обычн
version, затем выполняет `npm ci` , `npm run verify` ; backend дополнительно
version, затем выполняет `npm ci` , `npm run verify` ; backend дополнительно
выполняет `npm run test:e2e` . У каждого image OCI version/revision labels.
выполняет `npm run test:e2e` . У каждого image OCI version/revision labels.
Images публикуются как `nexus.ksv741.keenetic.pro /wealthledger/frontend:X.Y.Z`
Images публикуются как `nexus.personal.ksv741.ru/docker-hosted /wealthledger/frontend:X.Y.Z`
и `.../backend:X.Y.Z` . `latest` не создаётся.
и `.../backend:X.Y.Z` . `latest` не создаётся.
Workflow отказывается перезаписывать существующие release tags;
Workflow отказывается перезаписывать существующие release tags;
защита release tags и запрет параллельных publishers обязательны. Если одна
защита release tags и запрет параллельных publishers обязательны. Если одна
@@ -78,8 +80,8 @@ Workflow отказывается перезаписывать существу
Выберите опубликованные version tags, выполните pull и сравните OCI revision
Выберите опубликованные version tags, выполните pull и сравните OCI revision
с release commit. Выполните smoke с disposable data/config до первого server
с release commit. Выполните smoke с disposable data/config до первого server
rollout. В production env хранится пара полных image references, например
rollout. В `release.env` комплекта хранится пара полных image references, например
`nexus.ksv741.keenetic.pro /wealthledger/frontend:0.2.0 ` . Сохраните versions/revisions
`nexus.personal.ksv741.ru/docker-hosted /wealthledger/frontend:0.2.3 ` . Сохраните versions/revisions
в deployment record. Release tags не должны переназначаться другим images.
в deployment record. Release tags не должны переназначаться другим images.
## Перенос существующих images из Gitea
## Перенос существующих images из Gitea
@@ -94,11 +96,11 @@ rollout. В production env хранится пара полных image referenc
``` bash
``` bash
skopeo login git.personal.ksv741.ru
skopeo login git.personal.ksv741.ru
skopeo login nexus.ksv741.keenetic.pro
skopeo login nexus.personal.ksv741.ru
for component in frontend backend; do
for component in frontend backend; do
skopeo copy --all --preserve-digests \
skopeo copy --all --preserve-digests \
" docker://git.personal.ksv741.ru/wealthledger/ $component :0.2.0 " \
" docker://git.personal.ksv741.ru/wealthledger/ $component :0.2.0 " \
" docker://nexus.ksv741.keenetic.pro /wealthledger/ $component :0.2.0 " || exit 1
" docker://nexus.personal.ksv741.ru/docker-hosted /wealthledger/ $component :0.2.0 " || exit 1
done
done
```
```
@@ -120,150 +122,60 @@ SQLite и не меняет encryption keys. Не переносите Git relea
## Первая установка
## Первая установка
Создайте restricted environment file и data directory:
На подготовленном сервере оператор клонирует `deploy` , настраивает внешний
HTTPS и registry login, размещает server env, сертификат T-Bank и data directory
вне checkout. Для новой пустой базы команда вызывается с `--init` .
Для действующей установки сохраняются прежние данные и ключи, а первый переход
выполняется как полное обновление с backup. Отсутствующая БД без `--init` — ошибка.
Точные команды и разрешения каталогов приведены в README комплекта.
``` bash
Git-тег `deploy` закрепляет пару images через `release.env` . Версия комплекта
sudo install -d -m 0700 /srv/wealthledger
не обязана совпадать с версиями компонентов. Публикация image не выпускает
sudo install -d -m 0700 -o 1000 -g 1000 /srv/wealthledger/data
комплект и не обновляет сервер автоматически. Команда получает выбранный тег,
sudo install -m 0600 .env.compose.example /srv/wealthledger/production.env
загружает images до простоя и использует весь snapshot этого commit.
```
Дальнейшие команды выполняет оператор, имеющий доступ к 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` без помещения пароля в командную строку:
``` bash
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
## Backup при остановленном backend
До backend update проверьте совместимость новой версии с текущим frontend
Сначала останавливаются писатели, затем копируется весь каталог данных,
и схемой, а обратной версии — с новой схемой. При неизвестной совместимости
включая возможные WAL/SHM. Только успешная копия допускает миграции. Локальные
не обещайте image rollback. В выбранном rollout отсутствуют параллельные API.
backups находятся рядом с data directory, вне checkout, и автоматически
не удаляются. Незавершённая копия помечается `.partial-` и не подходит для restore.
Ниже data path соответствует примеру env; при другом path замените его .
Ключ шифрования хранится отдельно. Локальная копия не защищает от потери диска .
Сохраните текущую пару image references/revisions и отдельно — ключи.
``` bash
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
## Независимое обновление frontend
После проверки API compatibility замените только `FRONTEND_IMAGE` в env:
Если изменились только image или конфигурация frontend, backend не
пересоздаётся, backup и migration не выполняются. Возможен короткий перерыв
``` bash
на внешней точке входа; старым browser tabs может потребоваться reload.
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
## Независимое обновление backend
Выпол нит е stopped-backend backup выше. Замените только `BACKEND_IMAGE` в env.
Измене ние image или конфигурации backend запускает stop → backup → migration →
Последовательность не продолжаетс я после failed migration:
readiness. Неизменный frontend не пересоздаётся. Во врем я пр остоя возможен 502.
Ошибка migration блокирует старт, ошибка готовности останавливает новый backend;
ошибка frontend не останавливает исправный backend. Успех фиксируется только
после проверок запуска затронутых компонентов.
``` bash
Одна file lock защищает установку от параллельных команд. Маркер незавершённого
docker compose pull backend migration
обновления блокирует повтор до разбора оператором. Он содержит этап, версии
docker compose up --no-deps --force-recreate --abort-on-container-exit \
и путь к backup, но не секреты и не копию данных. После обновления нужен smoke
--exit-code-from migration migration &&
через HTTPS: healthcheck сам по себе не доказывает работу Secure refresh-cookie.
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
## Откат image
Для frontend верните предыдущий совместимый version tag и повторите frontend update.
Откат версии и восстановление базы — разные операции. Прежний backend можно
Для backend сначала остановите процесс и подтвердите совместимость с тар ого
запускать только после проверки совместимости с текущей схемой. Автоматическ ого
image с **текущей ** схемой. Верните предыдущий image reference и запустите только backend:
rollback нет; migration service при ручном image rollback не запускается.
Порядок ручных Compose-команд и проверки recovery находится в README комплекта.
``` bash
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
## Explicit restore
Это отдельное решение оператора, **не часть image rollback ** . Оператор выбирает
Оператор выбирает проверенную копию и явно принимает потерю всех более новых
backup timestamp и явно принимает потерю всех изменений после него, в ключая
записей. Сначала восстановление проверяется на isolated данных с прежним ключом
сессии, карты и подключения. Сначала воспроизведите restore на isolated project
и совместимым image. На целевой установке останавливаются писатели, текущий
с отдельным data path/port и совместимым image; не используйте production data
каталог сохраняется под новым именем, затем восстанавливается выбранная копия.
как fixture. Убедитесь в наличии соответствующего encryption key.
Backend пересоздаётся, чтобы заново подключить заменённый bind-каталог:
простой stop/start может сохранить mount прежних данных.
После отдельного решения оста новите backend и убедитесь, что migration н е
Проверяются цел остность БД, auth/cards и расшифровка подключения. Только посл е
работает. Сохраните нынешний каталог под новым именем, ничего не удаляя:
подтверждённого recovery можно снять маркер незавершённой операции.
``` bash
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 последовательность.