Docs security / security (pull_request) Successful in 6s
Docs verify / verify (pull_request) Successful in 11s
Conventional commits / commits (pull_request) Successful in 11s
Docs security / security (push) Successful in 7s
Docs verify / verify (push) Successful in 10s
228 lines
15 KiB
Markdown
228 lines
15 KiB
Markdown
# Контейнерная поставка
|
||
|
||
Runbook для одного Docker host, одного backend и краткого простоя.
|
||
[Топология](containers.md#контейнерная-поставка) и
|
||
[ADR 015](../adr/015-single-server-sqlite-deployment.md) объясняют границы.
|
||
Команды выполняются из 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 не обещаны.
|
||
- Gitea Container Registry; проверяемая версия сервера — 1.27.3.
|
||
Runner label — `ubuntu-latest`, runner должен иметь Docker CLI, buildx и daemon.
|
||
Наличие label само по себе не доказывает Docker capability.
|
||
- В Actions каждого component repo нужны `REGISTRY_USERNAME` и
|
||
`REGISTRY_TOKEN` отдельной identity с package write permission. Server
|
||
использует другую identity только с package read permission. 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 с исходниками обоих компонентов:
|
||
|
||
```bash
|
||
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 публикуются как `git.personal.ksv741.ru/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, например
|
||
`git.personal.ksv741.ru/wealthledger/frontend:0.1.0`. Сохраните versions/revisions
|
||
в deployment record. Release tags не должны переназначаться другим images.
|
||
|
||
## Первая установка
|
||
|
||
Создайте restricted environment file и data directory:
|
||
|
||
```bash
|
||
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 server identity без помещения token в командную строку:
|
||
|
||
```bash
|
||
docker login git.personal.ksv741.ru --username SERVER_PULL_USER
|
||
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 и отдельно — ключи.
|
||
|
||
```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
|
||
|
||
После проверки API compatibility замените только `FRONTEND_IMAGE` в env:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```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
|
||
|
||
Это отдельное решение оператора, **не часть image rollback**. Оператор выбирает
|
||
backup timestamp и явно принимает потерю всех изменений после него, включая
|
||
сессии, карты и подключения. Сначала воспроизведите restore на isolated project
|
||
с отдельным data path/port и совместимым image; не используйте production data
|
||
как fixture. Убедитесь в наличии соответствующего encryption key.
|
||
|
||
После отдельного решения остановите backend и убедитесь, что migration не
|
||
работает. Сохраните нынешний каталог под новым именем, ничего не удаляя:
|
||
|
||
```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 последовательность.
|