core code helper

CI/CD з GitHub Actions: тести, збірка образу й деплой

Повний пайплайн для вебзастосунку: перевірки на пул-реквест, збірка Docker-образу з кешем, публікація в реєстр і деплой з ручним підтвердженням для продакшену.

0 імпортували Імпортувати собі

CI/CD — це не «щоб було». Це спосіб доставляти зміни часто, маленькими частинами і без страху. GitHub Actions зручний тим, що живе поруч з кодом: пайплайн — це YAML-файл у репозиторії, який проходить code review разом з усім іншим.

Структура пайплайна

pull request ──► lint + тести ──► (зелено) ──► можна мерджити
push у main ──► тести ──► збірка образу ──► push у реєстр ──► деплой на staging
тег v* ──► деплой у production (з підтвердженням)

Перевірки на пул-реквест

# .github/workflows/ci.yml
name: ci
on:
  pull_request:
  push:
    branches: [main]

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true   # новий push скасовує застарілий прогін

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17-alpine
        env:
          POSTGRES_PASSWORD: test
        ports: ["5432:5432"]
        options: >-
          --health-cmd "pg_isready -U postgres"
          --health-interval 5s --health-retries 10
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.13"
          cache: pip
      - run: pip install -r requirements.txt -r requirements-dev.txt
      - run: ruff check .
      - run: pytest -q --maxfail=1
        env:
          DATABASE_URL: postgres://postgres:test@localhost:5432/postgres
Порада

concurrency з cancel-in-progress економить хвилини: якщо ви запушили тричі поспіль, перевірятиметься лише останній коміт.

Збірка і публікація образу

  image:
    needs: test
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write          # публікація в GitHub Container Registry
    outputs:
      tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: type=sha,format=short
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Тег образу — короткий SHA коміту. Так завжди відомо, який саме код працює на сервері, і відкат — це просто деплой попереднього тегу.

Важливо

Не використовуйте latest для деплою. Тег latest змінюється, а значить, два сервери з «однаковим» образом можуть запускати різний код.

Деплой з environments

Environments у GitHub дають окремі секрети для кожного середовища і правила захисту: обов'язкові рев'юери, дозволені гілки, таймер очікування.

  deploy-staging:
    needs: image
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy
        run: |
          curl -fsS -X POST "$DEPLOY_HOOK" \
            -H "Authorization: Bearer $DEPLOY_TOKEN" \
            -d "image=ghcr.io/${{ github.repository }}:${{ needs.image.outputs.tag }}"
        env:
          DEPLOY_HOOK: ${{ vars.DEPLOY_HOOK }}
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

  deploy-production:
    needs: [image, deploy-staging]
    runs-on: ubuntu-latest
    environment: production     # тут налаштовано обов'язкове підтвердження
    steps:
      - run: echo "Деплой ${{ needs.image.outputs.tag }} у production"

Безпека пайплайна

  • Мінімальні permissions на рівні workflow і розширення лише там, де треба (packages: write тільки для збірки).
  • OIDC замість довгоживучих ключів для хмар: GitHub видає короткоживучий токен, а AWS/GCP/Azure довіряють йому за умовами (репозиторій, гілка, environment).
  • Фіксуйте сторонні actions за SHA коміту, а не за тегом: тег можна перевісити на шкідливий код.
    - uses: some-org/some-action@3f1c9e2b7a5d...  # v2.1.0
  • Обережно з pull_request_target: він має доступ до секретів, і якщо в ньому запустити код з форку, секрети можуть витекти.
Обережно

Ніколи не підставляйте недовірені дані (назву гілки, заголовок PR, текст коміту) прямо в run: через ${{ }}. Це пряма ін'єкція команд. Передавайте їх через env: і використовуйте як змінні оболонки.

Швидкість

  • кешуйте залежності (cache: у setup-actions) і шари Docker (type=gha);
  • розпаралелюйте незалежні джоби — лінтер, юніт і інтеграційні тести можуть іти одночасно;
  • запускайте важкі перевірки лише при зміні відповідних файлів (on.pull_request.paths).
Чому для деплою краще тег з SHA коміту, а не latest?

SHA однозначно вказує на версію коду: видно, що саме задеплоєно, а відкат — це деплой попереднього SHA. latest перезаписується при кожній збірці, тож неможливо гарантувати, що на різних серверах і в різний час працює однаковий код.

Збережіть статтю собі

Увійдіть, щоб імпортувати її у власні нотатки, відмічати вивчене, ставити лайки й питати спільноту. Реєстрація безкоштовна: вхід через Google займає кілька секунд.

Увійти через Google Інші способи входу