Phase 2 ladder executed 2026-05-25 (~10min end-to-end, lowest → highest blast radius): proxy-chain (id 28) → imgproxy (29) → minio (30) → mongo (31) → books-db (32) → elasticsearch (33) All 6 smoked green. books-api + books-task-runner reconnected transparently through mongo + books-db recreate windows. ES snapshot repo `kreknin` preserved through bind. Phase 3: - created .wiki/concepts/portainer-stack-management-books-vds.md (pattern application + diffs from vds-kzntsv) - entity books-vds.md: 6 stacks flipped SSH-managed → Portainer-managed - index.md: new concept entry Pre-flight quirks documented (CentOS 7 yum dead, no jq, no docker login). Acceptance: 4/5 verified, backup pipeline (#5) verified by inspection (bind paths preserved) — full pipeline run pending next 06:00 MSK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.6 KiB
Portainer stack management on books VDS
Application of the canonical pattern from portainer-stack-management-vds к books VDS (89.253.255.133, host4g.ru, CentOS 7).
Canonical rule (same as VDS-infra): all docker-compose стеки на books VDS управляются через Portainer https://portainer.kzntsv.site (endpoint 1). SSH-compose в /usr/docker/<svc>/ — anti-pattern post-2026-05-25.
2026-05-25 retro-migrated 6 ранее SSH-compose стеков: proxy-chain, imgproxy (2 контейнера), minio, mongo, books-db, elasticsearch. Skipped traefik + portainer (management plane). Adapter script scripts/books-vds-portainer-migration/migrate.sh.
Diffs from VDS-infra pattern
| Aspect | vds-kzntsv | books VDS |
|---|---|---|
| Portainer URL | portainer.vds.kzntsv.site |
portainer.kzntsv.site |
| Auth | JWT через admin/password (PAT 401'd) | X-API-Key header — PAT works |
| Stack dir prefix | /opt/stacks/<stack>/ |
/usr/docker/<stack>/ |
| Endpoint ID | 1 (single docker daemon) | 1 (books VDS local). Endpoint 6 = stostayer — НЕ наш |
| Compose down | cd $DIR && docker compose down |
docker rm -f by com.docker.compose.project label |
env_file: directives |
many stacks had them | none — env was inline в compose |
.env files |
many stacks had them | none — все env inline |
| Sibling files | nginx.conf, certs, scripts | nginx.conf for imgproxy-nginx only |
| docker-compose binary | v2 plugin only | v2 plugin (2.27.1) + v1 standalone (1.29.2). Script uses neither — direct docker rm -f |
Pre-flight quirks on books VDS
- No
jqinstalled, no python3, yum is broken (CentOS 7 EOL since 2024-06, mirrors retired). Mitigation: pre-install static jq 1.7.1 binary:curl -fsSL https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 \ -o /usr/local/bin/jq && chmod +x /usr/local/bin/jq - No
/root/.docker/config.json— nodocker login registry.kzntsv.siteconfigured. Risk: if Portainer create force-pulls:latestfor private registry images (proxy-chain, books-*) → pull fail. Mitigation in practice: Portainer create использует local image cache when available; for current:latestcached images миграция прошла без issue. Future-proofing:docker login registry.kzntsv.sitebefore any registry-managed stack force-pull. - Mongo image lost tag — running container's image was retagged (digest
0bf62880fec3only). Portainer create pulls freshmongo:4.2— may be different sub-version digest. 4.2 schema stable, accepted as low risk. Mitigation if reproducibility critical: pin to digest.
Stack inventory (2026-05-25 post-migration, Portainer Ids)
Endpoint 1 (books VDS):
| Id | Name | Compose dir | Note |
|---|---|---|---|
| 22 | books-job-scheduler | (Portainer-only, pre-migration) | agenda jobs + embedded mongo |
| 23 | books-api | (Portainer-only) | books API server, Nitro, port 3021 |
| 24 | books-web | (Portainer-only) | books Nuxt front-end |
| 25 | books-ntfy | (Portainer-only) | local ntfy (separate from ntfy.vds.kzntsv.site) |
| 26 | books-ops-mcp | (Portainer-only) | + books-docker-proxy + books-docker-proxy-ro + bootstrap |
| 27 | chrome | (Portainer-only) | Puppeteer for PDFs/scraping |
| 28 | proxy-chain | /usr/docker/proxy-chain |
upstream HTTP-proxy chain, internal-only |
| 29 | imgproxy | /usr/docker/imgproxy |
imgproxy + imgproxy-nginx, serves imgproxy.kzntsv.site |
| 30 | minio | /usr/docker/minio |
S3 blob store, minio.kzntsv.site |
| 31 | mongo | /usr/docker/mongo |
shared MongoDB 4.2, used by books-api + books-task-runner |
| 32 | books-db | /usr/docker/books-db |
MariaDB latest, books application DB |
| 33 | elasticsearch | /usr/docker/elasticsearch |
ES 7.10.0 + path.repo=/snapshots for backup |
Non-Portainer (management plane, скип migration):
traefik—/usr/docker/traefik/, ad-hoc compose, бинд./letsencrypt(acme.json) +./data/traefik.ymlportainer—/usr/docker/portainer/, ad-hoc compose, named volumeportainer_data
Endpoint 6 (stostayer VDS, не наш — user-confirmed 2026-05-25). Stacks listed: Traefik, minio, mariadb, imgproxy, stostayer-{franchise,web,tireservice-api}, registry. Same Portainer UI manages, but physical machine not ours.
Migration ladder
Used 2026-05-25, lowest → highest blast radius. Successful in this order:
- proxy-chain — internal-only, no traefik exposure → smoke for the script
- imgproxy — 2-container compose with sibling nginx.conf
- minio — read-mostly, books-api retries OK
- mongo — shared, used by books-api + books-task-runner. Reconnect transparent (~13s)
- books-db — MariaDB, books-api connection-pool reconnect (~15s)
- elasticsearch — single-node, snapshot repo
krekninpreserved through bind
Все 6 stacks Up + smoke green в одну сессию (~10 минут end-to-end).
Gotchas hit during migration
All 8 gotchas from portainer-stack-management-vds applied; books-specific additions:
- CentOS 7 base OS, yum dead — package install через yum невозможно. Static jq binary download — единственный путь без EPEL mirror работающего. Documented above.
- No registry login —
docker login registry.kzntsv.siteне настроен. Кэшированные образы спасают, но fresh-pull для private images → auth fail. Setupdocker loginесли планируется image-rotation от Portainer UI. - Compose v1 + Compose v2 plugin co-existence —
docker-compose(1.29.2) иdocker compose(2.27.1) обе installed. Migration script используетdocker rm -fдля down → не зависит ни от той ни от другой. Чище.
Smoke commands per stack (post-migration)
# proxy-chain — internal smoke (HTTP 400 = service responding, not a valid proxy GET)
curl -sS -o /dev/null -w "%{http_code}\n" http://localhost:8778/
# imgproxy — traefik HTTP route
curl -ksS -o /dev/null -w "%{http_code}\n" https://imgproxy.kzntsv.site/
# minio
curl -ksS -o /dev/null -w "%{http_code}\n" https://minio.kzntsv.site/minio/health/live
# mongo (auth via root/MONGO_INITDB_ROOT_PASSWORD)
docker exec mongo mongo --quiet --eval 'db.adminCommand({ping:1}).ok' -u root -p '<from pass>'
# books-db
docker exec books-db mariadb -uroot -p'<from pass>' -e 'SELECT 1'
# elasticsearch
docker exec elasticsearch curl -sS localhost:9200/_cluster/health | jq .status
docker exec elasticsearch curl -sS localhost:9200/_snapshot # verify kreknin repo
What's preserved post-migration
- Bind data paths —
/usr/docker/<stack>/data(and./snapshots,./config, etc.) stay on host disk. Bind preserved through container recreate. - Backup pipeline —
scripts/books-vds-backup-daily-kreknin/run.shrsync paths unchanged. ESpath.repo=/snapshotsbind preserved → snapshot repokreknincontinues to write. - Traefik labels — propagate automatically. Routes for
imgproxy.kzntsv.site/minio.kzntsv.site/elasticsearch.kzntsv.sitere-attach as containers come up (docker socket events). - Network — все 6 stacks join external
proxynetwork as before. No re-configuration. - Compose file on disk —
/usr/docker/<stack>/docker-compose.ymlremains as reference. Дата-stamp backupdocker-compose.yml.bak-pre-portainer-migration-2026-05-25создан per stack.
What changed
working_dirlabel: было/usr/docker/<stack>(ad-hoc), стало/data/compose/<id>/(Portainer-managed). Compose files в/usr/docker/<stack>/теперь reference-only.- Lifecycle management: edit / restart / view logs — всё через Portainer UI или API. Editing
/usr/docker/<stack>/docker-compose.ymlнапрямую больше не имеет эффекта на runtime (Portainer владеет compose state в/data/compose/<id>/docker-compose.yml).
Cross-refs
- portainer-stack-management-vds — source pattern (vds-kzntsv).
- books-vds — host factsheet (updated 2026-05-25).
- books-vds-backup-daily-kreknin-2026-05-25 — backup pipeline stand-up; depends on bind paths preserved through this migration.
scripts/books-vds-portainer-migration/{migrate.sh,README.md}— implementation.