CI/CD — не Jenkins на 40 job’ов. Для личного и small-team git хватает короткого честного pipeline: проверка → артефакт → выкладка → smoke 🚦

Цели, не buzzwords #

  1. main всегда собирается.
  2. Деплой повторяемый (не «я с ноутбука rsync’нул»).
  3. Откат понятен (предыдущий артефакт / предыдущий commit).

Continuous Deployment на каждый push — только если smoke автоматический. Иначе — CD до staging, prod вручную/по tag.

Минимальные стадии #

lint/test → build → deploy → smoke
СтадияПримеры
lint/testhugo --minify, go test, shellcheck
builddocker image, static public/
deployrsync, kubectl, compose pull
smokecurl -f, health endpoint

На PR — lint/test (+ build без deploy). На main / tag — всё.

Секреты #

  • в secrets CI, не в yaml;
  • отдельный deploy-ключ без sudo;
  • rotate, если runner когда-либо светился.
# идея
env:
  SSH_KEY: ${{ secrets.SSH_KEY }}

Concurrency #

Не деплойте два push’а параллельно в один host:

concurrency:
  group: deploy-blog
  cancel-in-progress: false  # или true — осознанно

Pin всего #

FROM hugo:0.164.0
# не :latest
image: git.example.com/evgeny/blog:1.4.2

Digest — ещё лучше для prod.

Пример каркаса (Forgejo/GHA-like) #

on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: docker
    steps:
      - uses: actions/checkout@v4   # или ручной git fetch
      - run: hugo --minify
      - run: pagefind --site public
      - run: rsync -az --delete public/ deploy@host:/srv/www/site/
      - run: curl -fsS https://site/ >/dev/null

У вас на blog — ещё generate-og и кэш _gen; принцип тот же.

Антипаттерны #

НетДа
docker compose pull с ноутбука в prodCI с аудитом
latest вездеpin version
sudo chown после rsync--chmod + правильные группы
деплой без smokecurl -f / healthcheck
один job «make all» 40 минкэш + параллель тестов

Откат #

  1. Git revert / previous tag → pipeline.
  2. Или оставить previous release directory и symlink current.
  3. Для ВМ — PBS restore (редко, но путь должен быть).

Итог #

Короткий pipeline, secrets, pin, concurrency, smoke. Когда это зелёное месяц — добавляйте matrix и coverage, не наоборот 📦