Files
admin/.wiki/concepts/portainer-stack-management-books-vds.md
vitya 86c44c3db9 tasks(books-vds-stacks-to-portainer): close 🟢 — 6 stacks migrated SSH-compose → Portainer
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>
2026-05-25 10:27:23 +03:00

8.6 KiB
Raw Blame History

title: Portainer stack management on books VDS — pattern application + migration log status: live tags: [books-vds, portainer, docker-compose, ops, migration] related: books-vds, portainer-stack-management-vds, books-vds-backup-daily-kreknin-2026-05-25

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

  1. No jq installed, 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
    
  2. No /root/.docker/config.json — no docker login registry.kzntsv.site configured. Risk: if Portainer create force-pulls :latest for private registry images (proxy-chain, books-*) → pull fail. Mitigation in practice: Portainer create использует local image cache when available; for current :latest cached images миграция прошла без issue. Future-proofing: docker login registry.kzntsv.site before any registry-managed stack force-pull.
  3. Mongo image lost tag — running container's image was retagged (digest 0bf62880fec3 only). Portainer create pulls fresh mongo: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.yml
  • portainer/usr/docker/portainer/, ad-hoc compose, named volume portainer_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:

  1. proxy-chain — internal-only, no traefik exposure → smoke for the script
  2. imgproxy — 2-container compose with sibling nginx.conf
  3. minio — read-mostly, books-api retries OK
  4. mongo — shared, used by books-api + books-task-runner. Reconnect transparent (~13s)
  5. books-db — MariaDB, books-api connection-pool reconnect (~15s)
  6. elasticsearch — single-node, snapshot repo kreknin preserved 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:

  1. CentOS 7 base OS, yum dead — package install через yum невозможно. Static jq binary download — единственный путь без EPEL mirror работающего. Documented above.
  2. No registry logindocker login registry.kzntsv.site не настроен. Кэшированные образы спасают, но fresh-pull для private images → auth fail. Setup docker login если планируется image-rotation от Portainer UI.
  3. Compose v1 + Compose v2 plugin co-existencedocker-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 pipelinescripts/books-vds-backup-daily-kreknin/run.sh rsync paths unchanged. ES path.repo=/snapshots bind preserved → snapshot repo kreknin continues to write.
  • Traefik labels — propagate automatically. Routes for imgproxy.kzntsv.site / minio.kzntsv.site / elasticsearch.kzntsv.site re-attach as containers come up (docker socket events).
  • Network — все 6 stacks join external proxy network as before. No re-configuration.
  • Compose file on disk/usr/docker/<stack>/docker-compose.yml remains as reference. Дата-stamp backup docker-compose.yml.bak-pre-portainer-migration-2026-05-25 создан per stack.

What changed

  • working_dir label: было /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