--- 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//` — **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//` | `/usr/docker//` | | 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: ```bash 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: 9. **CentOS 7 base OS, yum dead** — package install через yum невозможно. Static jq binary download — единственный путь без EPEL mirror работающего. Documented above. 10. **No registry login** — `docker login registry.kzntsv.site` не настроен. Кэшированные образы спасают, но fresh-pull для private images → auth fail. Setup `docker login` если планируется image-rotation от Portainer UI. 11. **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) ```bash # 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 '' # books-db docker exec books-db mariadb -uroot -p'' -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//data` (and `./snapshots`, `./config`, etc.) stay on host disk. Bind preserved through container recreate. - **Backup pipeline** — `scripts/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//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/` (ad-hoc), стало `/data/compose//` (Portainer-managed). Compose files в `/usr/docker//` теперь reference-only. - Lifecycle management: edit / restart / view logs — всё через Portainer UI или API. Editing `/usr/docker//docker-compose.yml` напрямую больше не имеет эффекта на runtime (Portainer владеет compose state в `/data/compose//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.