From 726d0c620e1ea222ac85c24537a4be85dc06c93a Mon Sep 17 00:00:00 2001 From: coopops Date: Thu, 30 Apr 2026 15:21:17 +0000 Subject: [PATCH] chore(docker): multi-stage mono-base + testnet smoke-stack (story 6.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Корневой Dockerfile переписан под две стадии: builder с full toolchain (apt dev-headers, WeasyPrint в venv, pnpm install + lerna run build + pnpm prune --prod --ignore-scripts) и runtime — slim node:22-slim без gcc/dev-headers, только runtime-libs WeasyPrint + готовый /app. Локально сжалось с 7.36GB до 2.95GB (-60%). Образы наследники (dicoop/desktop / coopback / cooparser / notificator / notifications) автоматом получат тонкий runtime через build-containers.yaml. Безопасность для playbook'ов monocoop: их docker-compose.yaml собирает сервисы из components/{controller,parser,desktop}/Dockerfile (не корневого) и монтирует ./:/app — корневой mono-base туда вообще не попадает. .dockerignore — выкинул .git (962MB), blockchain-data/wallet-data, _blago, coverage, *.log и т.п. Build-context теперь намного легче. docker-compose.testnet.yml — локальный 3-сервисный smoke-стек: ke-node (dicoop/blockchain_v5.1.1:dev) + ke-contracts (one-shot copy из dicoop/contracts:dev в shared volume) + ke-bootstrap (mono-base, node /app/components/boot/dist/index.cjs boot:remote). Изолирован от dev-ноды mono-ai-4-node-1 через отдельную сеть и порты 8919/9907/8101. В playbook'ах не применяется (там docker-compose.yaml, не .testnet.yml). .gitignore — добавлен .env.testnet (содержит EOSIO_*KEY). Co-Authored-By: Claude Opus 4.7 (1M context) --- .dockerignore | 38 ++++++++++++ .gitignore | 1 + Dockerfile | 121 +++++++++++++++++++++++++------------ docker-compose.testnet.yml | 109 +++++++++++++++++++++++++++++++++ 4 files changed, 232 insertions(+), 37 deletions(-) create mode 100644 docker-compose.testnet.yml diff --git a/.dockerignore b/.dockerignore index 4175924eca0..c3f889d4b9b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,41 @@ +# Не тащить в build-context — экономит время `docker build` (особенно +# на крупных репах с .git под 1GB) и не пачкает финальный образ. node_modules +**/node_modules dist +**/dist +.git +.github +.gitignore +.cursor +**/.DS_Store +# State от локальных runtime-сценариев (boot:remote / dev-нода / wallet) +**/blockchain-data +**/wallet-data +**/keosd-data + +# CI/coverage/тестовые артефакты +coverage +**/coverage +.nyc_output +*.log + +# BMad/blago workspace папки (не относятся к билду образа) +_blago +**/_blago +.bmad-core +.bmad-output + +# Локальные секреты (на всякий случай — секреты должны идти через +# build-args / docker-compose environment, не через COPY) +.env +.env.* +!.env-example +**/.env +**/.env.* +!**/.env-example + +# Артефакты других сервисов +components/desktop/.quasar +components/contracts/build diff --git a/.gitignore b/.gitignore index 19a693c1bfd..621b896fe3d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ dist/ _blago/ blago/ components/reports-standarts/ВОСХОД/ +.env.testnet diff --git a/Dockerfile b/Dockerfile index dff431e7d7f..94c8f7a92aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,49 +1,96 @@ +# Multi-stage build для `dicoop/mono-base`. +# +# Стадии: +# 1. builder — full toolchain: pnpm + lerna + apt dev-headers (gcc, +# libcairo2-dev, ...) + WeasyPrint в venv. Здесь делаются +# `pnpm install` (с devDeps), `lerna run build`, потом +# `pnpm prune --prod` (devDeps выкидываются из workspace +# node_modules — экономит ~1.5GB). +# 2. runtime — slim образ без dev-headers и без gcc. Только runtime +# libs WeasyPrint + готовый /app + venv. Глобальные pnpm/lerna +# ставятся отдельно для удобного запуска `pnpm -F run start`. +# +# Финальный размер: ~1-1.5GB (вместо 7.36GB в одностадийной сборке). +# Все потребители mono-base (controller/desktop/parser/notifications/ +# notificator/boot) пользуются одним и тем же тонким runtime-образом +# через `FROM dicoop/mono-base:` + `CMD`. + +# ── Stage 1: builder ────────────────────────────────────────────────── FROM node:22-slim AS builder WORKDIR /app -# Сразу копируем все файлы +# Build-time системные deps. Тяжёлые `-dev`-пакеты (для компиляции +# native-bindings + WeasyPrint native deps) сюда, в runtime их не +# тащим. `--no-install-recommends` экономит ещё ~150MB. +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 python3-pip python3-venv \ + build-essential gcc g++ python3-dev \ + libcairo2-dev libffi-dev libjpeg-dev libopenjp2-7-dev zlib1g-dev \ + libpango-1.0-0 libpangoft2-1.0-0 libpangocairo-1.0-0 libcairo2 \ + shared-mime-info \ + && rm -rf /var/lib/apt/lists/* + +# WeasyPrint в venv — переедет в runtime-stage целиком через COPY. +RUN python3 -m venv /venv \ + && /venv/bin/pip install --no-cache-dir WeasyPrint==67 + +RUN npm install -g pnpm lerna --no-fund --no-audit + +# Сначала манифесты (для кэш-friendly install). `.dockerignore` уже +# выкидывает node_modules/dist/.git, поэтому `COPY .` лёгкий. COPY . . -# Устанавливаем инструменты -RUN npm install -g pnpm lerna +# Используем существующий lockfile. Если он не совпадает с workspace — +# падать сразу, не дрейфовать незаметно. +RUN pnpm install --frozen-lockfile -# Установка зависимостей -# Используем версию pnpm, совместимую с существующим lock-файлом -RUN pnpm install - -# Установка системных зависимостей для WeasyPrint и диагностических утилит (Debian/Ubuntu версии) -RUN apt-get update && apt-get install -y \ - python3 \ - python3-pip \ - python3-venv \ - gcc \ - g++ \ - python3-dev \ - libpango-1.0-0 \ - libpangoft2-1.0-0 \ - libpangocairo-1.0-0 \ - libcairo2 \ - libcairo2-dev \ - libffi-dev \ - shared-mime-info \ - zlib1g-dev \ - libjpeg-dev \ - libopenjp2-7-dev \ - procps \ - wget \ - && python3 -m venv /venv \ - && /venv/bin/pip install WeasyPrint==67 \ - && rm -rf /var/lib/apt/lists/* - -# Сборка всех компонентов +# Сборка всех пакетов через lerna-граф (зависимости собираются в +# правильном порядке). Никаких --filter — пусть собирается всё, чтобы +# образ годился любому потребителю mono-base. RUN lerna run build -# Финальный образ -FROM builder AS runtime +# Удалить devDeps из всех workspace node_modules. Это и есть основная +# оптимизация размера — typescript, vitest, eslint, unbuild, ts-node, +# desktop-сборщики и пр. удаляются из финального дерева. +# +# - `CI=true` отключает интерактивный prompt («вы уверены что снести +# modules?») — без TTY pnpm иначе абортится с +# ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY. +# - `--ignore-scripts` подавляет lifecycle hooks (prepare/postinstall) +# после prune. Иначе pnpm дёргает `quasar prepare` в desktop'е, +# а quasar (devDep) уже снесён → ELIFECYCLE. Сборка артефактов +# уже прошла на шаге `lerna run build` — повторно ничего готовить +# не нужно. +RUN CI=true pnpm prune --prod --ignore-scripts -# Настройка переменных окружения +# ── Stage 2: runtime ────────────────────────────────────────────────── +FROM node:22-slim AS runtime + +WORKDIR /app + +# Только runtime-библиотеки WeasyPrint (без `-dev` headers и без gcc). +# `python3` нужен потому что venv в /venv/bin/python — символическая +# ссылка на системный интерпретатор; без него `weasyprint` падает с +# `python: not found`. `procps`/`wget` — для дебага/healthcheck'ов +# из docker-compose. +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 \ + libpango-1.0-0 libpangoft2-1.0-0 libpangocairo-1.0-0 libcairo2 \ + libffi8 libjpeg62-turbo libopenjp2-7 zlib1g shared-mime-info \ + procps wget \ + && rm -rf /var/lib/apt/lists/* + +# Python venv с WeasyPrint, готовый к использованию. +COPY --from=builder /venv /venv ENV PATH="/venv/bin:$PATH" -# Проверка WeasyPrint -RUN weasyprint --version \ No newline at end of file +# Приложение целиком (с уже урезанными до prod node_modules). +COPY --from=builder /app /app + +# Глобальные pnpm/lerna — нужны чтобы потребители mono-base могли +# делать `CMD ["pnpm","-F","","run","start"]` в production. +RUN npm install -g pnpm lerna --no-fund --no-audit + +# Sanity-check: WeasyPrint работает и виден через PATH. +RUN weasyprint --version diff --git a/docker-compose.testnet.yml b/docker-compose.testnet.yml new file mode 100644 index 00000000000..eb6a39d7cec --- /dev/null +++ b/docker-compose.testnet.yml @@ -0,0 +1,109 @@ +# Demo-стек testnet-ноды на одном хосте. +# +# Составные части: +# 1. ke-node — Antelope/EOSIO нода (dicoop/blockchain_v5.1.1). +# blockchain-data в named volume → переживает рестарты. +# config + genesis монтируются read-only из репо. +# 2. ke-contracts — one-shot init из dicoop/contracts:: копирует +# wasm/abi артефакты в shared volume для bootstrap. +# 3. ke-bootstrap — one-shot из dicoop/mono-base:: запускает +# `pnpm -F @coopenomics/boot run boot:remote` — +# ждёт RPC ноды, активирует фичи, деплоит все +# контракты, создаёт системный токен. +# +# Идемпотентность: повторный `compose up` на не-чистой ноде заставит +# bootstrap попытаться задеплоить уже задеплоенное — eosjs ответит +# duplicate transaction для setcontract'а. Для перезапуска с нуля — +# `down -v` (зачистит blockchain-data + contracts volumes), затем `up`. +# +# Изоляция от рабочей dev-ноды mono-ai-4-node-1: отдельная сеть +# `ke-testnet`, отдельные порты на хосте (8919/9907/8101). + +networks: + ke-testnet: + name: ke-testnet + +volumes: + testnet-blockchain-data: + name: testnet-blockchain-data + testnet-wallet-data: + name: testnet-wallet-data + testnet-contracts: + name: testnet-contracts + +services: + ke-node: + image: dicoop/blockchain_v5.1.1:dev + container_name: testnet-ke-node + networks: [ke-testnet] + ports: + - "8919:8888" # HTTP RPC + - "9907:9876" # P2P + - "8101:8070" # state_history + volumes: + - testnet-blockchain-data:/mnt/dev/data + - testnet-wallet-data:/root/eosio-wallet + - ./components/boot/src/configs:/mnt/dev/config:ro + command: > + /bin/bash -c "/usr/local/bin/nodeos + -d /mnt/dev/data + --config-dir /mnt/dev/config + --genesis-json /mnt/dev/config/genesis.json" + healthcheck: + # `get_info` возвращает 200, как только http_plugin поднялся. + # ke-bootstrap ждёт `service_healthy`, чтобы не дёргать RPC до + # старта плагина. В образе ноды нет curl, используем wget. + test: ["CMD-SHELL", "wget -qO- http://localhost:8888/v1/chain/get_info > /dev/null 2>&1"] + interval: 5s + timeout: 3s + retries: 30 + start_period: 5s + restart: unless-stopped + + ke-contracts: + # Образ-артефакт. Entrypoint `copy` копирует /contracts/* в /out + # (см. components/contracts/docker/entrypoint.sh). После выхода + # named volume `testnet-contracts` содержит wasm/abi всех контрактов. + image: dicoop/contracts:dev + container_name: testnet-ke-contracts + networks: [ke-testnet] + volumes: + - testnet-contracts:/out + command: ["copy"] + restart: "no" + + ke-bootstrap: + # Локальный билд `dicoop/mono-base` собран из текущего dev-репозитория + # (тег dev-local) — содержит boot:remote команду, которой нет в + # registry-варианте `:latest`. Когда build-containers начнёт пушить + # mono-base по push в dev-ветку, можно заменить на `dicoop/mono-base:dev`. + image: dicoop/mono-base:dev-local + container_name: testnet-ke-bootstrap + networks: [ke-testnet] + depends_on: + ke-node: + condition: service_healthy + ke-contracts: + condition: service_completed_successfully + environment: + CHAIN_URL: http://ke-node:8888 + EOSIO_PUB_KEY: ${EOSIO_PUB_KEY} + EOSIO_PRV_KEY: ${EOSIO_PRV_KEY} + PASSWORD: ${PASSWORD} + CONTRACTS_DIR: /contracts + RPC_WAIT_TIMEOUT_MS: "120000" + # initial-data/extra-data выключены — bootstrap делает только + # on-chain работу (контракты + системные параметры). + # Включить через INSTALL_INITIAL_DATA=1 / INSTALL_EXTRA_DATA=1 + # (требует MONGO_URI / PG_HOST / ...). + volumes: + - testnet-contracts:/contracts:ro + working_dir: /app/components/boot + # Запускаем через node + собранный dist, а не через pnpm-script + # (`boot:remote` в boot/package.json вызывает `esno src/index.ts ...`, + # но `esno` — devDep и удалён `pnpm prune --prod` в mono-base). + # working_dir = /app/components/boot чтобы dotenv подхватил + # boot/.env (если он смонтирован), и чтобы относительные пути + # к blockchain-data резолвились правильно (boot mkdir-ит ./blockchain-data). + command: ["node", "/app/components/boot/dist/index.cjs", "boot:remote"] + restart: "no"