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 или developruns-on: ubuntu-latest— билдим на Linux (C# под .NET Core/8+ работает везде)setup-dotnet— ставит нужную версию .NET SDKdotnet 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 пакетов
Каждый запу