--- title: mappa — VDS deploy runbook (БД / образ / Portainer-стек 26 / редеплой / verify) type: concept tags: [mappa, vds, deploy, docker, portainer, traefik, postgres, runbook, meta] related: [[../entities/vds-kzntsv]], [[portainer-stack-management-vds]], [[mappa-prod-infra]] updated: 2026-08-23 --- # mappa → VDS deploy runbook Развёртывание **mappa** (единый meta-сервис цеха: HTTP-ядро + MCP-адаптер, Postgres) на [[../entities/vds-kzntsv]] (89.253.255.94) рядом с общей БД (решения 1/17/18 спеки `concepts/mappa`). Первый деплой — 2026-08-23 (#1012, закрыта). **Разделение труда (директива 2026-08-23):** деплой/редеплой = АДМИН (таска + письмо); прод-импорт, verify счётчиков, read-only+фолбэк, ntfy-алерт, артефакты, MCP-регистрация = mappa (#985). Не делать чужую часть. ## Артефакты - **Код:** `OpeItcLoc03/mappa` @ master. Сборка TS: `npm ci && npm run build` → `dist/`. - **Образ:** `registry.kzntsv.site/mappa:0.1.0`. Dockerfile в корне репо (multi-stage node:22-alpine; **обязательный COPY `src/lib/schema.sql` → `dist/src/lib/schema.sql`** — tsc не копирует .sql, без него migrate() падает «schema.sql not found»). - **Стек Portainer:** `mappa` (Id 26, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/mappa.compose.yml`. - **БД:** база `mappa` + role `mappa` на общем `postgres:16` VDS (owner = mappa). Креды: **`pass show mappa/full-env`** (`MAPPA_DB_PASS`, `DATABASE_URL` docker-сетевой: `postgres://mappa:…@postgres:5432/mappa`; внешний host `postgres.vds.kzntsv.site:5432` — TLS-verify-skip). - **Endpoint:** `https://mappa.vds.kzntsv.site` (traefik websecure, letsEncrypt, service-port 8731). ## Реденплой (обновление образа) — быстрый путь ```bash # 1. Собрать и запушить новый тег образа (с воркстейшена, Docker Desktop) cd ~/projects/mappa docker build -t registry.kzntsv.site/mappa: . docker push registry.kzntsv.site/mappa: # 2. НА VDS: docker pull ДО restart стека (иначе застрянет на старом digest!) ssh vds.kzntsv.site 'docker pull registry.kzntsv.site/mappa:' # 3. Поменять image: в host-stacks/vds-kzntsv/mappa.compose.yml И в Portainer stack 26 # (обе копии синхронизировать — иначе следующий redeploy из гита откатит тег) # 4. Restart стека через Portainer API (НЕ ad-hoc docker compose up!) # JWT: pass vds-kzntsv/full-env PORTAINER_ADMIN_PASS → POST /api/auth curl -ksS -X POST "https://portainer.vds.kzntsv.site/api/stacks/26/restart?endpointId=1" \ -H "Authorization: Bearer $JWT" # 5. Verify curl -ksS https://mappa.vds.kzntsv.site/health # {"ok":true,"service":"mappa"} curl -ksS https://mappa.vds.kzntsv.site/admin/status # entities/by_type/projects curl -ksS https://mappa.vds.kzntsv.site/graph/stats # nodes/edges/components ssh vds.kzntsv.site 'docker ps --filter name=mappa --format "{{.Status}}"' ``` ## Обновление env (пароль БД и т.п.) Через Portainer → Stack 26 → Edit (env array), НЕ файлом на VDS (Portainer string-mode не материализует .env; env передаётся API-массивом — gotcha 1 `portainer-stack-management-vds`). `MAPPA_DB_PASS` — единственный env в стеке. ## Первичный деплой с нуля (cold start) 1. **БД**: на VDS `docker exec postgres psql -U postgres`: `CREATE ROLE mappa LOGIN PASSWORD ''; CREATE DATABASE mappa OWNER mappa;` Пароль → `pass insert -m mappa/full-env`. 2. **Образ**: собрать (см. выше), `docker push`. 3. **Compose**: `admin/host-stacks/vds-kzntsv/mappa.compose.yml` → Portainer create (standalone string-mode, endpointId=1, env `[{"name":"MAPPA_DB_PASS","value":…}]`). 4. **Проверка миграций**: при старте migrate() сам создаёт таблицы (6 шт: projects/counters/ entities/edges/leases/logs, owner=mappa) — `docker logs mappa` + `\dt` в БД. 5. **Пинг mappa**: письмо в `mappa/.agents/inbox/` (event: mappa-deployed) — импорт и остальное за ними. ## Мониторинг живости (монитор → ntfy) Остаток #985 (решение 15/18: алерт при падении сервиса) — реализован #1013 (2026-08-23). - **Скрипт:** `/root/mappa-ntfy-monitor/monitor.py` (python3 stdlib; источник: `admin/scripts/mappa-ntfy-monitor/monitor.py`). - **Проверка:** GET `https://mappa.vds.kzntsv.site/health` — HTTP 200 + JSON `{"ok":true,"service":"mappa"}` (таймаут 15s). - **Алерт:** ntfy топик `mappa-alerts` (basic-auth vitya, admin-role → rw все топики), после 3 подряд фейлов (~15 мин), раз за эпизод (dedupe); recovery-сообщение при возврате. - **Крон:** `/etc/cron.d/mappa-ntfy-monitor` — `*/5 * * * *` (root, лог `/var/log/mappa-ntfy-monitor.log`). - **Конфиг:** `/root/.mappa-ntfy-monitor.env` (600; NTFY_* из `pass vds-kzntsv/full-env`). State: `/root/.mappa-ntfy-monitor.state`. - ⚠️ Монитор НЕ трогает стек 26 — только наблюдение; редеплой/рестарт — отдельной задачей. ## Gotchas (из первых деплоев) 1. **schema.sql в dist** — tsc компилирует только .ts; `migrate()` ищет `.sql` рядом с `dist/src/lib/`. Без COPY в Dockerfile → контейнер в restart-loop («schema.sql not found»). 2. **docker pull до restart** — Portainer start/stop НЕ тянет новый digest сам по себе; перезалитый `:0.1.0` без pull на VDS = старый образ (ловил 2× за сессию 2026-08-23). 3. **Не ad-hoc compose** — все стеки VDS через Portainer (VDS ops rule). `docker rm -f` вручную снёс контейнер — Portainer recreate поднял, но это не путь. 4. **mem_limit 512m** — канон app-стеков, не убирать (cgroup-лимит vs вся память хоста). 5. **Внешний DATABASE_URL** — контейнерный `@postgres:5432` валиден только внутри docker-сети; для импорта с воркстейшена — `postgres.vds.kzntsv.site:5432` (+ TLS-verify-skip, self-signed). 6. **Закрытие деплоя** — всегда пара «таска + письмо mappa» (канон `mappa-deploy-workflow`).