deploy-service
Skill1 starsChanged 3 months agoMITView on GitHub
Деплой/обновление/откат инфраструктурного сервиса по паттерну push-to-pull (мак → git push → SSH → git pull --ff-only → docker compose up -d). Блокирующий pre-check: inventory свежий, бэкап ≤24ч, домен резолвится, working tree чистый. Yellow Zone, брифинг + type-to-confirm, rollback <5 мин. MODE: new | update | rollback. Триггеры: «задеплой», «новый сервис», «обнови сервис», «deploy», «выкатить», «накатить версию», «откати». НЕ для деплоя в обход git (аварийный runbook); НЕ для drop volume/database (Red Zone).
- Reads credentials
- Deletes or force-pushes
- Commits and pushes
3 other projects use a version of this.
Adopted by
Most recently taken up by
- NVIDIA/SkillSpectortheir own version, first seen Oct 13, 2026
- tejastn10/workbenchtheir own version, first seen Oct 13, 2026
- dream-horizon-org/pulsetheir own version, first seen Oct 8, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- NVIDIA/SkillSpector★ 25ksame name
- dream-horizon-org/pulse★ 44same name
- tejastn10/workbench★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository25 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- audit-securitySkill
- bootstrap-new-serverSkill
- cleanup-existing-serverSkill
- configure-vpn-routingSkill
- devSkill
- extract-subscription-serversSkill
- finalize-vpn-routingSkill
- generate-client-configSkill
- health-checkSkill
- install-monitoring-stackSkill
- inventory-scanSkill
- migrate-server-to-serverSkill
- refresh-vpn-knowledgeSkill
- restore-from-backupSkill
- retroSkill
- rotate-secretsSkill
- second-opinionSkill
- setup-backupsSkill
- setup-happ-subscriptionSkill
- setup-secrets-vaultSkill
- setup-server-proxySkill
- setup-vpn-panelSkill
- sysadmin-initSkill
- sysadmin-meetSkill
The file
Files
<role>
Я выкатываю инфраструктурный сервис через git-based pipeline (push-to-pull —
ADR 0015 проекта-носителя или эквивалентное решение), защищая прод от
случайных правок «на сервере». Каждый деплой — Yellow Zone: брифинг 6 пунктов, явное
подтверждение, заранее готовый rollback за <5 мин. Прямые правки на сервере вне git —
аварийный режим, не моя норма.
</role><context>
Предполагается:
- Git-репозиторий `infra` настроен с deploy-pipeline (см. ADR 0015 проекта-носителя
или эквивалент): инфраструктурный репо склонирован в `$INFRA_DIR` на сервере
(стандартное соглашение — `/opt/infra/`, путь конфигурируется), есть deploy-key,
SSH-алиас сервера в `~/.ssh/config`, рабочий `scripts/deploy/deploy-remote.sh`
и серверный `$INFRA_DIR/deploy.sh`.
- Скилл `setup-secrets-vault` использован для хранения секретов (`.env` через Keychain/SOPS).
- Skилл `inventory-scan` запускался недавно — `inventory/shared/services.md` актуален.
- **Сетевая раскладка нового сервиса соответствует эталону**
`.claude/knowledge/networking/_reference/server-networks-defaults.md`
(4-сеть-модель в §3, decision tree в §4, антипаттерны в §1.2/§2/§6/§8).
Шаблон `templates/docker-compose-skeleton.yml` уже содержит три раскладки
(A/B/C) с прямыми ссылками на разделы эталона.НЕ предполагается:
- Автоматический CI/CD (отвергнут в ADR 0015 проекта-носителя для milestone v1; в вашем проекте может быть актуален раньше — см. условия выбора в
references/deploy-paradigms.md). - Прямые правки на сервере как норма (drift = аварийный режим).
- Деплой приложенческих сервисов (например, веб-приложений и ботов проекта-носителя) — они обычно живут в своих репозиториях со своими pipeline; этот скилл — только для инфраструктурных сервисов.
</context>
<goals>
После выполнения:
- ✓ Сервис задеплоен, контейнер `Up (healthy)`
- ✓ Если есть домен — nginx vhost работает, TLS валиден, HTTP 200/3xx
- ✓ `inventory/shared/services.md` обновлён (через `inventory-scan` или вручную)
- ✓ Rollback готов и проверен (есть предыдущий git ref, скрипт `rollback.sh` отлажен)
- ✓ Логи последних 30 секунд без ERROR/FATAL
</goals>Параметры
| Параметр | Required | Default | Описание |
SERVICE_NAME | да | — | Имя сервиса в kebab-case (например, my-app, kuma) |
MODE | нет | update | new (новый сервис) / update / rollback |
DEPLOY_PATH | нет | /opt/infra/services/<name> | Путь на сервере |
DOMAIN | нет | — | Домен для nginx vhost (если нужен публичный доступ) |
SOURCE_REPO | нет | — | URL git-репо с docker-compose.yml (если MODE=new) |
IMAGE | нет | — | Образ для MODE=new (ghost:5.87.2) — pin tag, без :latest |
PORT | нет | — | Внутренний порт контейнера (для nginx upstream) |
Все параметры — env vars или CLI-аргументы. Никаких хардкодных IP/доменов в скрипте.
Процедура
Шаг 1: Pre-check (Зелёная зона, БЛОКЕР если не пройден)
Запускается до любых изменений. Любая красная проверка останавливает процедуру.
| Проверка | Команда | Норма |
inventory/shared/services.md свежий | find inventory/snapshots/ -mtime -7 | head -1 | есть файл за 7 дней |
| Последний бэкап свежий | restic snapshots --latest 1 --json | jq -r '.[0].time' | возраст ≤24 ч |
| Working tree чистый (мак) | git status --porcelain | пусто |
| Local main = origin/main | git fetch && git rev-parse main vs origin/main | равны |
Если MODE=new и DOMAIN: домен резолвится | dig +short $DOMAIN | возвращает IP сервера |
| SSH-доступ к серверу | ssh "$SERVER" "echo ok" | ok |
| Сетевая раскладка compose соответствует эталону | визуальный review services/<SERVICE_NAME>/docker-compose.yml | networks: явно указана (не default bridge); ports: — либо отсутствует, либо с префиксом 127.0.0.1:; нет network_mode: host без ADR; нет монтирования /var/run/docker.sock без :ro (см. эталон _reference/server-networks-defaults.md §1.2/§2.1/§6/§8) |
Если хоть одна красная — остановиться, вернуть оператору отчёт «pre-check провален» с конкретным шагом для исправления. НЕ начинать деплой «в надежде, что обойдётся».
Шаг 2: Decision Tree — new vs update vs rollback
MODE=new→ Шаги 3a (создание структуры из templates) → 4 (брифинг) → 5 (push-to-pull) → 5.5 (приёмка артефакта до переключения) → 6 (smoke) → 7 (nginx + TLS, еслиDOMAIN) → 8 (inventory)MODE=update→ Шаг 3b (diff текущего состояния и нового) → 4 (брифинг с упором на что меняется) → 5 (push-to-pull) → 5.5 (приёмка артефакта) → 6 (smoke) → 8 (inventory)MODE=rollback→ Шаг 3c (выбрать prev git ref) → 4 (брифинг отката) → 5 (rollback.sh с этим ref) → 6 (smoke). Шаг 5.5 пропускается: откатываемся на состояние, которое уже стояло на бою
Шаг 3a: Создание нового сервиса (MODE=new)
mkdir -p services/<SERVICE_NAME>
cp .claude/skills/deploy-service/templates/docker-compose-skeleton.yml \
services/<SERVICE_NAME>/docker-compose.yml
cp .claude/skills/deploy-service/templates/env-example-skeleton \
services/<SERVICE_NAME>/.env.example
[ -n "$DOMAIN" ] && cp .claude/skills/deploy-service/templates/nginx-vhost-skeleton.conf \
services/nginx/sites-available/<DOMAIN>.confЗатем заменить плейсхолдеры (<SERVICE_NAME>, <IMAGE>, <PORT>, <DOMAIN>) на реальные
значения через sed -i или Edit. На сервере создать .env (не в git!) — секреты подставляются
из Keychain через скилл setup-secrets-vault.
Шаг 3b: Diff для обновления (MODE=update)
cd /opt/infra
git fetch origin main
git log --oneline HEAD..origin/main -- services/<SERVICE_NAME>/ # что изменилось
git diff HEAD origin/main -- services/<SERVICE_NAME>/ # содержимое измененийПоказать оператору diff. Если image: тег меняется — обязательно проверить changelog образа
(см. Failed Attempts ниже).
Шаг 3c: Выбор ref для rollback (MODE=rollback)
cd /opt/infra
git log --oneline -10 -- services/<SERVICE_NAME>/ # последние 10 коммитов сервиса
# оператор выбирает SHA, на который откатываемсяШаг 4: Yellow Zone брифинг 6 пунктов
Перед запуском Шага 5 — обязательный брифинг (регламент Yellow Zone, см. персону .claude/agents/sysadmin.md):
- ЧТО: какой сервис, какие файлы изменяются (показать
git diffsummary) - ЗАЧЕМ: новый функционал / security patch / откат проблемы / обновление версии
- РИСКИ: ожидаемый downtime (
docker compose up -dдля одного контейнера ~10-30 сек), что зависит (depends_onконтейнеры), что может упасть - ОТКАТ: конкретная команда
cd /opt/infra && git checkout <prev-sha> && ./deploy.sh(дляMODE=rollback— наоборот, текущий ref как страховка) - СТРАХОВКА: snapshot inventory есть, бэкап БД (если затрагивается) свежий ≤24ч
- ПРОВЕРКА: конкретный healthcheck endpoint, ожидаемый HTTP-код, что искать в логах
Затем запросить type-to-confirm с уникальной строкой:
deploy-<SERVICE_NAME>-<YYYYMMDD-HHMM> — оператор должен ввести ровно её.
Шаг 5: Push-to-pull deploy (паттерн ADR 0015 проекта-носителя или эквивалент)
# На локальной машине
cd <repo-root-of-infra>
git add services/<SERVICE_NAME>/ [services/nginx/sites-available/<DOMAIN>.conf]
git commit -m "feat(<SERVICE_NAME>): deploy v<X.Y.Z>"
git push origin main
# Триггер деплоя
./scripts/deploy/deploy-remote.shСкрипт deploy-remote.sh (см. scripts/deploy/deploy-remote.sh в репо) делает pre-flight
(local main == origin/main), идёт по SSH в /opt/infra/, вызывает серверный deploy.sh,
который делает git pull --ff-only + селективный docker compose up -d по diff.
Для MODE=rollback используется scripts/rollback.sh из этого скилла — он делает
git checkout <prev-sha> вместо git pull и затем тот же docker compose up -d.
Шаг 5.5: Приёмка артефакта ДО переключения (блокер по ресурсам)
Шаг 1 проверяет окружение — свежесть inventory, бэкап, чистое дерево, домен. Содержимое
того, что реально поедет на сервер, до сих пор не проверял никто. А там живёт целый класс
дефектов, невидимых ни локально, ни в репозитории: файл есть в рабочей копии, выглядит
целым, но в выкатку не едет — помечен export-ignore, отфильтрован сборщиком, не попал в
образ. Ссылка на него при этом остаётся, и посетитель видит пустые рамки.
Ожог (отчёт пользователя агента, 2026-08-05): выкатка статического сайта перевела картинки в
WebP и убрала исходники из архива через export-ignore. Девять блоков на главной ссылались
на убранные файлы. Локально всё открывалось, репозиторий был чист, приёмка — зелёная.
Принцип: судить о том, что будет на бою, по артефакту, а не по репозиторию.
Поэтому проверка встраивается в сам скрипт выкатки — между «собрали/распаковали» и «переключили каталоги», а не выполняется мной постфактум: при push-to-pull подмена происходит внутри серверного скрипта, после неё проверять поздно.
| Что выкатываем | Что сверяем с артефактом |
| статика | ссылки на ресурсы: есть ли файл в распакованном каталоге сборки |
| образ | переменные окружения и точки монтирования, которых образ ждёт |
| пакет | зависимости, которых он требует |
Две реакции, не одна. Без разделения проверка бесполезна: живой сайт почти всегда ссылается на ещё не сделанные разделы, блокирующая проверка рвала бы каждую выкатку — и её снимут первой же рукой, а вместе с ней уйдёт защита от настоящего дефекта.
| Класс | Реакция | Почему |
| битый ресурс (картинка, стиль, скрипт, шрифт, документ) | 🔴 рвём выкатку до переключения | симптом сорвавшегося export-ignore; посетитель увидит пустые рамки |
| битая ссылка на страницу/раздел | строка в лог, не блокер | навигация на не сделанные разделы — обычное состояние живого сайта |
Ключевая деталь реализации: пути ищутся строковыми литералами, а не разбором по тегам.
Содержимое кавычек любого вида и url(...), одинаково в .html, .js и .css. Разбор по
атрибутам разметки слеп к путям внутри скриптов на самой странице — на этом и погибли те
девять ссылок. Пути, собранные в коде из кусков (${dir}/x.webp), пропускаются осознанно:
статически они не проверяются, и блокировать по ним значило бы обещать больше, чем проверка
умеет.
Саму проверку испытать на историческом дефекте — коммит до починки, а не только на
здоровом каталоге и синтетике (якорь §3.11, references/character-anchors.md). Прогон на
здоровом состоянии доказывает, что код исполняется, а не что проверка видит дефект.
Не встроена в скрипт выкатки — задаю оператору три вопроса вслух и отвечаю на них фактами, а не предположением: что именно едет, чем это отличается от стоящего сейчас, и не ссылается ли выкатываемое на то, чего в нём нет.
Шаг 6: Smoke-test после деплоя
Ждать 30 секунд (контейнер запускается, healthcheck стартует), затем:
ssh "$SERVER" '
docker ps --filter name=<SERVICE_NAME> --format "{{.Names}} {{.Status}}"
docker logs --since=30s <SERVICE_NAME> 2>&1 | grep -iE "error|fatal|panic" | head -10
'
# Если есть домен
curl -s -o /dev/null -w "%{http_code}\n" --max-time 10 "https://<DOMAIN>/healthz" \
|| curl -s -o /dev/null -w "%{http_code}\n" --max-time 10 "https://<DOMAIN>/"Ожидаемое:
StatusсодержитUpи(healthy)(если есть healthcheck)- В логах нет
ERROR/FATAL/panic - HTTP-код 200 или 3xx
Если хоть что-то не сошлось → автоматический rollback (см. Шаг 5 для MODE=rollback).
Шаг 7: Nginx + TLS (только MODE=new с DOMAIN)
После того как контейнер поднят и отвечает:
ssh "$SERVER" '
ln -sf "$INFRA_DIR/services/nginx/sites-available/<DOMAIN>.conf" \
/etc/nginx/sites-enabled/<DOMAIN>.conf
nginx -t && systemctl reload nginx
acme.sh --issue --webroot /var/www/html -d <DOMAIN>
acme.sh --install-cert -d <DOMAIN> ... # подробности — см. references/deploy-paradigms.md
systemctl reload nginx
'См. шаблон nginx-vhost: templates/nginx-vhost-skeleton.conf.
Шаг 8: Обновление inventory
# Запуск скилла inventory-scan (предпочтительно)
@inventory-scan
# Или ручное обновление (минимум)
# Edit inventory/shared/services.md — добавить строку про новый сервис
git add inventory/shared/services.md
git commit -m "docs: добавлен <SERVICE_NAME> в services.md"
git pushFailed Attempts
Реальные грабли, документированные на этом проекте:
- «rsync вместо git pull» — антипаттерн, drift'ы между сервером и репо. Принцип
«только git как источник истины» зафиксирован в ADR 0015 проекта-носителя
или эквивалентном решении. См. рассказ в
references/deploy-paradigms.md. - «compose без явной
networks:секции» → default bridge — контейнер уезжает на встроеннуюdocker0сеть, DNS по имени сервиса не работает,nginx → proxy_pass http://my-app:3000отдаётhost not found. Лечение — явная user-defined сеть (networks: [services]), эталон_reference/server-networks-defaults.md§1.2. - «
internal: true+ports: ["127.0.0.1:5432:5432"]на одном контейнере» —psql -h 127.0.0.1не подключается, хотя порт «вроде бы опубликован». Причина:internal: trueблокирует DNAT-правила Docker. Лечение — подключить контейнер ко второй сети без internal (паттерн postgres-в-двух-сетях, эталон §3.3 и §5.1). - «
network_mode: hostдля веб-сервиса, чтобы быстро» — снимает изоляцию, ломает observability сетей, мешает diff'у inventory, не даёт UFW работать через docker-сети. Брать только с обоснованием в ADR; легитимные кейсы — UDP с большим диапазоном портов (FRP, Beszel-hub UDP 45876). Эталон §6. - «docker compose up -d без аргумента
<service>» — поднимает ВСЕ сервисы compose-файла. Опасно для shared-stacks (например, исторический compose может содержать postgres+redis, которыми пользуются другие БД проекта). Всегда указывать конкретный сервис:docker compose up -d my-app. - «git push --force после правки на сервере» — уничтожает ту самую правку, которую делали напрямую. NEVER. Сначала забрать правку с сервера в репо, потом push.
- «деплой без healthcheck в compose» — контейнер
Up, но приложение внутри упало. Smoke-test не поймает. Healthcheck в compose обязателен (см. шаблон). - «Compose при смене
image:строки пересоздаёт контейнер всегда» — сравнение делается по строке тега, не по digest. Например, заменаredis:7-alpine→redis:7.4.8-alpineпересоздаёт все redis-контейнеры, хотя image-id тот же. Если нужен узкий scope — использовать--no-depsи переименование точечно. - «Compose без явного
image:, толькоbuild:» при смене service key триггерит полную пересборку из Dockerfile (наблюдалось до 8 минут простоя на средних образах). Решение: добавитьimage: <project>-<service>:latestявно перед переименованием. - «acme.sh обновил cert, но nginx ссылается на старый путь» — паттерн зафиксирован
в ADR 0008 проекта-носителя или эквивалентном решении. После certbot/acme —
обязательно
nginx -s reloadили скрипт-обёртка типаcert-reload-smart.sh.
Граничные случаи
- Новый сервис без домена (внутренний бот, worker) → пропустить Шаг 7 (nginx).
- Обновление с breaking changes БД-схемы → это Red Zone, не Yellow. Сначала миграция отдельно (отдельный runbook), потом деплой кода.
- Сервис не в shared compose, а в отдельном compose-файле в /opt/
<service>→ старый паттерн, путь к compose другой. Если возможно — мигрировать в/opt/infra/services/в рамках следующего деплоя (отдельный план). - Rollback после успешного deploy (мониторинг показал проблему через час) → запустить
bash .claude/skills/deploy-service/scripts/rollback.sh <SERVICE_NAME> <prev-sha>за <5 мин.
Идемпотентность
docker compose up -d сам по себе идемпотентен: если состояние совпадает с желаемым — ничего
не делает. Скрипт deploy.sh на сервере использует --ff-only — повторный запуск без новых
коммитов не делает изменений. Безопасно перезапускать всю процедуру.
Параметризация
Никаких хардкодных IP/доменов/сервисов в этом SKILL.md и скриптах:
- IP сервера → SSH-алиас (имя
prod/production/ любое — настраивается оператором один раз в~/.ssh/config); в скрипте используется переменная$SERVER - Путь к инфра-репо на сервере → переменная
$INFRA_DIR(стандартное соглашение/opt/infra/, путь конфигурируется) - Домен сервиса → переменная
DOMAIN(передаётся из брифинга) - Имя сервиса → переменная
SERVICE_NAME - Источник секретов → менеджер паролей оператора через скилл
setup-secrets-vault
Bundled resources
scripts/deploy.sh— обёртка вокругscripts/deploy/deploy-remote.shрепо (для запуска из скилла)scripts/deploy-remote.sh— копия рабочего скрипта для self-contained вызоваscripts/rollback.sh— откат на конкретный git reftemplates/docker-compose-skeleton.yml— заготовка с pinned image, mem_limit, healthcheck, networks, labelstemplates/nginx-vhost-skeleton.conf— TLS + proxy_pass + security headerstemplates/env-example-skeleton— секреты как ссылки на Keychain (не значения)references/deploy-paradigms.md— разбор rsync (антипаттерн) vs push-to-pull (ADR 0015 или эквивалент) vs CI/CD
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

