# books-vds-portainer-migration Migrates SSH-compose stacks at `/usr/docker//` on books VDS (`89.253.255.133`) to Portainer-managed via API. Adapted from canonical pattern: [`.wiki/concepts/portainer-stack-management-vds.md`](../../.wiki/concepts/portainer-stack-management-vds.md). Related task: [`books-vds-stacks-to-portainer`](../../.tasks/books-vds-stacks-to-portainer.md). ## Usage ```bash # 1. Get PAT from pass export BOOKS_PORTAINER_API_KEY=$(pass show books-vds/full-env BOOKS_PORTAINER_API_KEY) # 2. Copy script to VDS scp -i ~/.ssh/id_ed25519_books_ops migrate.sh root@89.253.255.133:/tmp/ # 3. Migrate one stack at a time, verify between ssh -i ~/.ssh/id_ed25519_books_ops root@89.253.255.133 \ "BOOKS_PORTAINER_API_KEY='$BOOKS_PORTAINER_API_KEY' bash /tmp/migrate.sh proxy-chain" ``` ## Ladder (lowest → highest blast radius) 1. **proxy-chain** — internal-only, no traefik exposure → smoke for the script 2. **imgproxy** — 2-container compose, serves `imgproxy.kzntsv.site` 3. **minio** — read-mostly, books-api retries OK 4. **mongo** — shared, used by books-api + books-task-runner. **VERIFY retry-tolerance first** 5. **books-db** — MariaDB, books-api connection-pool reconnects 6. **elasticsearch** — currently "not used" per user. Verify before recreate. ## Smoke per stack (after each migration) ```bash # proxy-chain — internal smoke (no public endpoint) docker exec proxy-chain wget -qO- localhost:8000 || echo "$?" # imgproxy — serves imgproxy.kzntsv.site curl -sS -o /dev/null -w "%{http_code}\n" https://imgproxy.kzntsv.site/ # minio curl -sS -o /dev/null -w "%{http_code}\n" https://minio.kzntsv.site/minio/health/live # mongo docker exec mongo mongo --quiet --eval 'db.adminCommand({ping:1}).ok' \ -u root -p "$(pass show books-vds/full-env | grep MONGO_PWD)" # books-db docker exec books-db mariadb -uroot -p"$(pass show books-vds/full-env | grep BOOKS_DB_PWD)" -e 'SELECT 1' # elasticsearch docker exec elasticsearch curl -sS localhost:9200/_cluster/health | jq .status ``` ## What the script does 1. **Backup** `docker-compose.yml.bak-pre-portainer-migration-` (idempotent — keeps first backup). 2. **Transform** compose: strip `env_file:` (defensive — books VDS has none), absolutize `./` paths to `/usr/docker//`. 3. **Parse** `.env` into API env array (no-op for books VDS — no `.env` files). 4. **Delete** existing Portainer stack with same name on endpoint 1 (idempotency for re-runs). 5. **Down** ad-hoc containers via `docker rm -f` by `com.docker.compose.project` label (bypasses docker-compose v1 `ContainerConfig` bug). 6. **POST** `/api/stacks/create/standalone/string?endpointId=1` — Portainer pulls images, creates network attachments, applies traefik labels through docker socket events. ## Diffs from VDS-infra script (`portainer-stack-management-vds.md`) | Aspect | VDS-infra | books VDS | |---|---|---| | Portainer URL | `portainer.vds.kzntsv.site` | `portainer.kzntsv.site` | | Endpoint ID | 1 (same) | 1 | | Stack dir prefix | `/opt/stacks//` | `/usr/docker//` | | Auth | JWT (PAT 401'd) | **X-API-Key** (PAT works) | | Compose down | `cd $DIR && docker compose down` | **`docker rm -f` by label** (skips compose binary entirely) | | `env_file:` strip | required (most stacks had .env) | defensive (books VDS has none) | ## Safety - **Re-runnable.** Delete-then-create idempotency means re-running for the same stack just rebuilds it (data binds preserved, names preserved, network re-attached). - **Compose file preserved on disk** (in `/usr/docker//` + dated backup). Portainer manages lifecycle but doesn't move the file. - **Bind paths preserved** — `/usr/docker//data` etc. stay at the same disk path. Backup pipeline (`scripts/books-vds-backup-daily-kreknin/`) doesn't need adjustment. - **Skips traefik / portainer** by name — refuses to migrate management plane. ## Post-migration - Update [`.wiki/entities/books-vds.md`](../../.wiki/entities/books-vds.md) — flip each stack from "SSH-managed" to "Portainer-managed" table. - Extend or fork [`.wiki/concepts/portainer-stack-management-vds.md`](../../.wiki/concepts/portainer-stack-management-vds.md) with books VDS section + gotchas if new ones encountered. - Close task as 🟢.