CI/CD для C#: настройка pipeline с нуля для ленивых и занятых

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

CI/CD для C#: как перестать деплоить вручную и начать жить

Если вы до сих пор собираете C# проект локально, заливаете билды по FTP и молитесь, чтобы ничего не упало — у меня для вас новости. В 2025 году так делать стыдно. Даже для маленького pet-проекта.

CI/CD — это не магия сеньоров за 300к, а просто набор скриптов, которые делают рутину за вас. Сегодня мы настроим полноценный CI/CD pipeline для C# с нуля. Без воды, с реальными примерами и разбором ошибок.

Кстати, если вы пишете на Python — у нас на pythonlib.ru тоже есть гайды по автоматизации. Но сегодня работаем с C#.

Что такое CI/CD и зачем он C# разработчику

CI (Continuous Integration) — это когда каждый ваш коммит в Git автоматически собирается и тестируется. Никаких «а у меня работает».

CD (Continuous Delivery/Deployment) — это когда после успешной сборки код сам едет на сервер (или в облако).

Для C# это особенно актуально: сборка через dotnet build может отличаться на разных машинах, а тесты забывают запускать вообще все. CI/CD решает обе проблемы.

Выбираем инструмент: GitHub Actions vs GitLab CI vs Jenkins

Для старта я рекомендую GitHub Actions. Почему?

  • Бесплатно для публичных репозиториев (да, для C# тоже)
  • Не нужно ставить сервер
  • Готовые экшены под .NET
  • YAML — читается легко

Если у вас GitLab — принцип тот же, синтаксис похож. Jenkins оставим для корпоративного ада.

Пишем первый pipeline для C# проекта

Создаём в корне репозитория файл .github/workflows/dotnet.yml. Да, файл должен быть именно в этой папке.

Вот минимальный рабочий pipeline, который собирает проект и прогоняет тесты:

name: .NET CI

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

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '8.0.x'

    - name: Restore dependencies
      run: dotnet restore

    - name: Build
      run: dotnet build --no-restore --configuration Release

    - name: Run tests
      run: dotnet test --no-build --configuration Release --verbosity normal

Разберём по шагам:

  • on: push — pipeline запускается при пуше в main или develop
  • runs-on: ubuntu-latest — билдим на Linux (C# под .NET Core/8+ работает везде)
  • setup-dotnet — ставит нужную версию .NET SDK
  • dotnet restore — восстанавливает NuGet пакеты
  • dotnet build — компилирует релизную версию
  • dotnet test — запускает все тесты из проекта

После пуша зайдите во вкладку Actions вашего репозитория — увидите, как pipeline выполняется. Если тесты упадут — получите красный статус и письмо на почту.

Добавляем code coverage (потому что тесты без покрытия — фикция)

Красивый pipeline должен показывать, сколько кода покрыто тестами. Для C# используем coverlet в связке с ReportGenerator.

Сначала добавьте в тестовый проект NuGet пакеты:

dotnet add YourTestProject.csproj package coverlet.collector
dotnet add YourTestProject.csproj package coverlet.msbuild

Теперь обновим шаг с тестами в pipeline:

    - name: Run tests with coverage
      run: |
        dotnet test --no-build --configuration Release \
          /p:CollectCoverage=true \
          /p:CoverletOutputFormat=opencover \
          /p:CoverletOutput=./TestResults/coverage.xml

    - name: Install ReportGenerator
      run: dotnet tool install -g dotnet-reportgenerator-globaltool

    - name: Generate coverage report
      run: reportgenerator \
        -reports:./TestResults/coverage.xml \
        -targetdir:./TestResults/coverage-report \
        -reporttypes:Html

    - name: Upload coverage report
      uses: actions/upload-artifact@v4
      with:
        name: coverage-report
        path: ./TestResults/coverage-report

После выполнения вы сможете скачать HTML-отчёт с покрытием через интерфейс GitHub Actions. Удобно для code review.

CI/CD для C#: настройка деплоя на сервер

Теперь самое интересное — автоматический деплой после успешной сборки. Рассмотрим вариант с деплоем на VPS через SSH.

Сначала сгенерируйте SSH-ключи и добавьте публичный ключ на сервер. Приватный ключ сохраните в Secrets вашего репозитория (Settings → Secrets and variables → Actions). Назовём его SSH_PRIVATE_KEY.

Добавляем job для деплоя:

  deploy:
    needs: build-and-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
    - uses: actions/checkout@v4

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '8.0.x'

    - name: Publish
      run: dotnet publish -c Release -o ./publish

    - name: Deploy via SSH
      uses: easingthemes/ssh-deploy@v4
      with:
        SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
        ARGS: "-avz --delete"
        SOURCE: "./publish/"
        REMOTE_HOST: "your-server.com"
        REMOTE_USER: "deploy"
        TARGET: "/var/www/myapp"
        
    - name: Restart service
      uses: appleboy/ssh-action@v1
      with:
        host: "your-server.com"
        username: "deploy"
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        script: |
          sudo systemctl restart myapp.service
          echo "App restarted successfully"

Важные моменты:

  • needs: build-and-test — деплой запустится только если сборка и тесты прошли успешно
  • if: github.ref == 'refs/heads/main' — деплоим только с main ветки
  • ssh-deploy — копирует файлы на сервер
  • appleboy/ssh-action — выполняет команды на сервере (рестарт службы)

Замените your-server.com, deploy и пути на свои. Убедитесь, что у пользователя на сервере есть права на перезапуск systemd службы.

Оптимизация: кэширование NuGet пакетов

Каждый запу

Блоги

Рекомендаций по книгам