# Локальная сборка образов вместо Yandex Container Registry - Дата: 2026-07-25 ## Контекст Три приложения собственной разработки — homepage, transcriber, remembos — деплоились через Yandex Container Registry. Схема у всех одинаковая: образ собирается локально, пушится в `cr.yandex/...` отдельным плейбуком (`playbook-*-registry.yml`), плейбук деплоя делает `docker_login` с `yc_oauth_token`, а compose тянет образ по registry-пути с тегом, зашитым в git. Схема тянула за собой три проблемы. **Секрет несопоставимого радиуса.** Ради одного `docker login` в vault лежал OAuth-токен всего аккаунта Yandex Cloud — реестр, виртуалки, биллинг. 24 июля он вместе с ещё 52 переменными ушёл открытым текстом в транскрипт: цена одной такой утечки — весь аккаунт, а не один сервис. **Разнесённый деплой.** Сборка жила в `task deploy` репозитория приложения, который сам звал `ansible-playbook`. У transcriber этот таск дополнительно правил `vars/transcriber.images.yml` в чужом репозитории через `yq` и делал там `git commit` с новым тегом. У remembos registry-плейбука не было вовсе — образ пушился руками, а в compose стоял тег `v0.2.0`, вбитый в git. У homepage тег был плавающий `:latest`, то есть ночной `docker image prune` мог подтянуть другое содержимое без возможности отката. **Автоматизировать сборку пока нечем.** Стабильного онлайн-раннера для workflow gitea у нас нет (отдельная задача — раннер on-demand), так что уехать в CI сборки не могут. Плюс после переезда сервера в Timeweb реестр остался единственной ниткой, тянущейся в Yandex Cloud. ## Решение Приложения переведены на общую роль `app_image` (канон — `~/projects/private/ansible-roles`, в репозитории лежит завендоренная копия в `roles/`, синхронизация — `inv roles-pull`/`roles-push`). Роль: 1. генерит случайный `BUILD_ID` — он же тег образа; 2. запускает в директории приложения его собственную команду сборки (контракт — `task image`, `BUILD_ID` приходит через окружение), приложение обязано собрать полный образ и затегать его `:$BUILD_ID`; 3. печатает размер архива, везёт его на сервер через `docker save` → `copy` → `docker load`; 4. отдаёт факт `app_image_tag`, которым compose-шаблон подставляет образ (`pull_policy: never` — в реестре образа нет). Два мотива за этим. Первый — **упростить деплой**, пока нет стабильного онлайн-раннера: теперь `inv pl -- ` собирает, доставляет и запускает приложение одной командой, отдельного ритуала в репозитории приложения не осталось. Второй — **сократить набор секретов**: реестр и OAuth-токен выпали из схемы, `yc_oauth_token`, `yc_container_registry` и `yc_container_registry_repository` удалены из vault (коммит `6a0d786`). Registry-плейбуки и `vars/transcriber.images.yml` удалены; у remembos заодно снесены gitea-workflow с goreleaser — они существовали только ради пуша образа в `cr.yandex`. ## Последствия - `+` Из деплоя ушли внешний реестр и токен на весь аккаунт облака; в vault на три переменные меньше. - `+` Деплой любого из трёх приложений — одна команда `inv pl -- `; «правка тега в чужом репозитории + коммит» как шаг деплоя исчезла. - `+` Тег каждого деплоя уникален, плавающего `:latest` больше нет. Откат — повторный деплой нужного коммита приложения. - `-` Дедупликации по слоям нет: каждый деплой везёт полный архив. Замеры `docker save`: homepage — 61 МБ, transcriber — 73 МБ, remembos — 21 МБ. Для ручного нечастого деплоя размен принят осознанно. - `-` Для деплоя нужен ноутбук с docker и тулчейнами: с чужой машины задеплоить нельзя, а `playbook-all-applications.yml` теперь собирает три образа за прогон. - `-` На сервере копятся образы от каждого прогона; чистит их ночной `docker image prune -af` (см. `playbook-system.yml`). - Контракт для новых приложений: чтобы деплоиться этой ролью, приложение должно иметь `task image`, тегающий образ из `$BUILD_ID`. - Осталось: `buckland/playbook-trackers.yml` всё ещё ходит в `cr.yandex` со своей копией токена в собственном vault — пока он жив, отзывать токен в облаке рано.