$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогGitHub Actions на практике: автоматизируем тесты, линтинг и деплой за один вечер

GitHub Actions на практике: автоматизируем тесты, линтинг и деплой за один вечер

$ sudo teach IT
·18 июля 2026 г.·9 мин·62
GitHub Actions на практике: автоматизируем тесты, линтинг и деплой за один вечер

Пишем реальный workflow с нуля: запуск pytest, проверка кода через Ruff и автоматический деплой на сервер при пуше в main. Разбираем структуру YAML-конфига и типичные ошибки.

Содержание
Что такое GitHub Actions и как это работаетСтруктура YAML-файла: разбираем по частямПишем реальный workflow: тесты, линтинг, деплойШаг 1. Тестирование с pytestШаг 2. Линтинг через RuffШаг 3. Деплой на сервер через SSHСобираем всё вместеТипичные ошибки и как их избежатьСекреты попадают в логиДеплой запускается при PR из форкаКэш pip не работаетYAML-отступыДеплой без откатаМатрица версий и параллельные тестыМониторинг и уведомленияКогда GitHub Actions не хватаетИтог

Большинство разработчиков знают, что CI/CD — это правильно. Но между «знать» и «настроить рабочий пайплайн» лежит пропасть из документации, непонятных YAML-ошибок и вопроса «а почему оно не задеплоилось?». GitHub Actions закрывает этот разрыв лучше, чем большинство альтернатив: инструмент встроен прямо в репозиторий, бесплатен для публичных проектов и не требует отдельного сервера под Jenkins.

В этой статье мы пройдём весь путь с нуля: напишем workflow, который запускает тесты через pytest, проверяет стиль кода через Ruff и деплоит приложение на сервер при пуше в ветку main. По ходу разберём структуру YAML-конфига и разберём типичные ошибки, которые стоят людям часов отладки.


Что такое GitHub Actions и как это работает

GitHub Actions — это платформа автоматизации, встроенная в GitHub. Вы описываете логику в YAML-файлах, которые лежат в директории .github/workflows/ вашего репозитория. GitHub читает эти файлы и запускает описанные действия на своих (или ваших) серверах в ответ на события: пуш, pull request, расписание, ручной триггер.

Ключевые понятия:

  • Workflow — весь файл автоматизации. Один репозиторий может иметь несколько workflow.
  • Job — единица работы внутри workflow. Jobs по умолчанию выполняются параллельно, но можно задать зависимости.
  • Step — конкретный шаг внутри job: запуск команды или использование готового action.
  • Action — переиспользуемый модуль. Например, actions/checkout клонирует репозиторий, actions/setup-python устанавливает нужную версию Python.
  • Runner — виртуальная машина, на которой выполняется job. GitHub предоставляет Ubuntu, Windows и macOS.

Важный момент: каждый job запускается в чистом окружении. Файлы, установленные в одном job, недоступны в другом — если только вы явно не передаёте их через артефакты или кэш.


Структура YAML-файла: разбираем по частям

Прежде чем писать конкретный пайплайн, стоит понять анатомию workflow-файла. Вот минимальный скелет:

code
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run something
        run: echo "Hello, CI"

Разберём каждый блок:

  • name — отображаемое имя workflow в интерфейсе GitHub.
  • on — триггеры. Здесь мы запускаем пайплайн при пуше в main или develop, а также при создании pull request в main.
  • jobs — словарь jobs. Ключ (build) — произвольное имя.
  • runs-on — тип runner. ubuntu-latest — самый распространённый выбор.
  • steps — список шагов. Каждый шаг либо использует готовый action (uses), либо выполняет shell-команду (run).

Пишем реальный workflow: тесты, линтинг, деплой

Допустим, у нас есть Python-проект с такой структурой:

code
my-app/
├── .github/
│   └── workflows/
│       └── ci.yml
├── app/
│   └── main.py
├── tests/
│   └── test_main.py
├── requirements.txt
└── pyproject.toml

Наша цель — один файл ci.yml, который:

  1. Запускает тесты через pytest.
  2. Проверяет код через Ruff.
  3. Деплоит на сервер через SSH, если пуш идёт в main.

Шаг 1. Тестирование с pytest

code
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    name: Run Tests
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install pytest

      - name: Run pytest
        run: pytest tests/ -v --tb=short

Несколько важных деталей:

cache: "pip" в setup-python — это встроенное кэширование зависимостей. GitHub кэширует директорию pip-кэша между запусками, и повторная установка пакетов занимает секунды вместо минут. Многие забывают про этот параметр и потом жалуются на медленные пайплайны.

--tb=short в pytest сокращает вывод трейсбека. В CI-логах это важно: полный трейсбек на сотни строк труднее читать.

Если у вас зависимости для тестов вынесены в отдельный файл (например, requirements-dev.txt), установите оба:

code
- name: Install dependencies
  run: |
    pip install -r requirements.txt
    pip install -r requirements-dev.txt

Шаг 2. Линтинг через Ruff

Ruff — линтер и форматтер для Python, написанный на Rust. Он в десятки раз быстрее flake8 и заменяет сразу несколько инструментов. Добавляем отдельный job:

code
  lint:
    name: Lint with Ruff
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"

      - name: Install Ruff
        run: pip install ruff

      - name: Run Ruff linter
        run: ruff check . --output-format=github

      - name: Run Ruff formatter check
        run: ruff format --check .

--output-format=github — специальный флаг, который форматирует вывод ошибок так, что GitHub автоматически аннотирует их прямо в diff pull request. Это удобно: разработчик видит замечания линтера прямо в коде, а не в логах.

ruff format --check — проверяет форматирование без изменения файлов. Если код не отформатирован, шаг завершится с ненулевым кодом и job упадёт.

Настройки Ruff лучше держать в pyproject.toml:

code
[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "W", "I", "N"]
ignore = ["E501"]

Шаг 3. Деплой на сервер через SSH

Это самая интересная и одновременно самая опасная часть. Деплой запускается только при пуше в main — не при pull request.

Сначала нужно добавить секреты в репозиторий. Идём в Settings → Secrets and variables → Actions и добавляем:

  • SSH_PRIVATE_KEY — приватный SSH-ключ для подключения к серверу.
  • SSH_HOST — IP-адрес или домен сервера.
  • SSH_USER — пользователь на сервере.
  • SSH_PORT — порт SSH (обычно 22).

Теперь сам job деплоя:

code
  deploy:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: [test, lint]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts

      - name: Deploy via SSH
        run: |
          ssh -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'EOF'
            cd /var/www/my-app
            git pull origin main
            source venv/bin/activate
            pip install -r requirements.txt
            sudo systemctl restart my-app
          EOF

Разберём критически важные детали:

needs: [test, lint] — деплой запустится только если оба предыдущих job завершились успешно. Это гарантирует, что сломанный код не попадёт на продакшн.

if: github.ref == 'refs/heads/main' && github.event_name == 'push' — двойная проверка. github.ref гарантирует, что мы деплоим только из main. github.event_name == 'push' исключает запуск при pull request (даже если PR нацелен на main).

chmod 600 ~/.ssh/id_rsa — обязательный шаг. SSH отказывается использовать ключ с широкими правами доступа.

ssh-keyscan — добавляет fingerprint сервера в known_hosts. Без этого SSH спросит подтверждение при первом подключении, и скрипт зависнет.

Heredoc с << 'EOF' — одинарные кавычки вокруг EOF важны: они предотвращают интерполяцию переменных на стороне GitHub Actions. Все переменные внутри блока будут раскрыты уже на сервере.


Собираем всё вместе

Финальный файл .github/workflows/ci.yml:

code
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    name: Run Tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"
      - run: pip install -r requirements.txt pytest
      - run: pytest tests/ -v --tb=short

  lint:
    name: Lint with Ruff
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"
      - run: pip install ruff
      - run: ruff check . --output-format=github
      - run: ruff format --check .

  deploy:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: [test, lint]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
      - uses: actions/checkout@v4
      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
      - name: Deploy
        run: |
          ssh -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'EOF'
            cd /var/www/my-app
            git pull origin main
            source venv/bin/activate
            pip install -r requirements.txt
            sudo systemctl restart my-app
          EOF

Типичные ошибки и как их избежать

Секреты попадают в логи

Никогда не используйте echo для вывода секретов в логи. GitHub маскирует значения из secrets.*, но если вы присвоите секрет обычной переменной и выведете её — маскировка не сработает. Правило простое: секреты только в secrets.*, никогда в env напрямую без необходимости.

Деплой запускается при PR из форка

Если репозиторий публичный, pull request из форка не имеет доступа к секретам — это защита GitHub. Но если вы неправильно настроили условие if, job деплоя может попытаться запуститься и упасть с непонятной ошибкой. Всегда проверяйте github.event_name.

Кэш pip не работает

Частая причина — requirements.txt генерируется динамически или его нет в корне. Кэш pip привязан к хэшу файла зависимостей. Если файл меняется при каждом запуске, кэш будет инвалидироваться постоянно.

YAML-отступы

YAML чувствителен к отступам. Самая частая ошибка — смешивание табов и пробелов, или неправильное выравнивание вложенных блоков. Используйте линтер для YAML (например, расширение для VS Code) и валидатор вроде actionlint — он проверяет синтаксис GitHub Actions специфично.

Деплой без отката

Простой git pull и перезапуск сервиса — это хорошо для старта, но в продакшне стоит думать об откате. Если pip install упал на середине, сервис может не запуститься. Рассмотрите blue-green деплой или хотя бы проверку статуса после рестарта:

code
sudo systemctl restart my-app
sleep 3
systemctl is-active --quiet my-app || exit 1

Матрица версий и параллельные тесты

Если нужно тестировать на нескольких версиях Python, GitHub Actions поддерживает матричную стратегию:

code
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12"]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: "pip"
      - run: pip install -r requirements.txt pytest
      - run: pytest tests/ -v

GitHub запустит три параллельных job — по одному на каждую версию. Это бесплатно и занимает столько же времени, сколько один запуск.


Мониторинг и уведомления

Базовый пайплайн работает, но вы хотите знать о падениях. GitHub по умолчанию присылает email при падении workflow. Для Slack или Telegram можно добавить шаг в конце job:

code
      - name: Notify on failure
        if: failure()
        uses: slackapi/slack-github-action@v1.27.0
        with:
          payload: |
            {
              "text": "Pipeline failed in ${{ github.repository }} on branch ${{ github.ref_name }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

if: failure() — условие, которое срабатывает только если предыдущие шаги упали. Это стандартный паттерн для уведомлений об ошибках.


Когда GitHub Actions не хватает

GitHub Actions отлично подходит для большинства проектов, но у него есть ограничения. Бесплатный план даёт 2000 минут в месяц для приватных репозиториев. Сложные пайплайны с долгими тестами быстро исчерпывают лимит. В таких случаях рассматривают self-hosted runners — собственные серверы, зарегистрированные как runner в GitHub.

Также GitHub Actions не идеален для очень сложных пайплайнов с динамической генерацией jobs, условными ветками и сложными зависимостями — там лучше смотреть в сторону Argo Workflows или Tekton. Но для 90% проектов описанного подхода более чем достаточно.

Если вы хотите разобраться в CI/CD системно — не только с GitHub Actions, но и с концепциями pipeline as code, управлением секретами, стратегиями деплоя и мониторингом — стоит посмотреть на структурированные материалы. Один из вариантов — курс «CI-CD от теории до практики», который охватывает эту тему от основ до production-ready конфигураций.


Итог

За один вечер можно получить рабочий пайплайн, который автоматически проверяет код и деплоит его на сервер. Ключевые принципы, которые стоит запомнить:

  • Разделяйте jobs по ответственности: тесты, линтинг, деплой — отдельно.
  • Используйте needs для управления зависимостями между jobs.
  • Всегда проверяйте условие деплоя через github.ref и github.event_name.
  • Кэшируйте зависимости — это экономит минуты на каждом запуске.
  • Храните чувствительные данные только в секретах репозитория.

GitHub Actions — это не магия и не сложная инфраструктура. Это YAML-файл с чёткой логикой. Как только вы понимаете структуру, написать пайплайн под любой проект становится вопросом получаса. Если хочется разобраться глубже — в том числе с более сложными сценариями деплоя и production-практиками — курс «CI-CD от теории до практики» может стать хорошей следующей точкой.

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

CI-CD от теории до практики

Курс по теме

CI-CD от теории до практики

Вы освоите работу с GitLab Cloud и GitHub Actions, научитесь настраивать раннеры, писать конфигурационные файлы, интегрировать CI/CD с Docker и автоматизировать деплой.

Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость
python

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость

Поймём, где чаще всего ломается воспроизводимость (версии данных, фильтры, дрейф) и как оформить пайплайн признаков так, чтобы результаты совпадали между запусками и окружениями.

17 сентября 2026 г.
1040
Компьютерное зрение на практике: детектируем объекты на видео с помощью OpenCV и Python
компьютерное

Компьютерное зрение на практике: детектируем объекты на видео с помощью OpenCV и Python

Шаг за шагом строим пайплайн обработки видеопотока: захват кадров, фоновое вычитание, трекинг объектов. Весь код — рабочий, с объяснением каждого параметра.

18 июля 2026 г.
670
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1070
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Что такое GitHub Actions и как это работаетСтруктура YAML-файла: разбираем по частямПишем реальный workflow: тесты, линтинг, деплойШаг 1. Тестирование с pytestШаг 2. Линтинг через RuffШаг 3. Деплой на сервер через SSHСобираем всё вместеТипичные ошибки и как их избежатьСекреты попадают в логиДеплой запускается при PR из форкаКэш pip не работаетYAML-отступыДеплой без откатаМатрица версий и параллельные тестыМониторинг и уведомленияКогда GitHub Actions не хватаетИтог