Всем привет! Когда я узнаю, что человек передо мной начинает изучать c# - первым делом я его спрашиваю, как ему язык, на чем раньше программировал и прочее. И в какой то момент разговоры доходят до докера\пайплайнов => многие ребята (которые не пробовали это раньше) начинают нехотя избегать эту тему, считая её чересчур скучной, странной и вообще "это уже какой то девопс". Хотя на деле - зная базово, что значат папки в твоем проекте на компе - можно освоить базовые основы красивой работы докера (+ можно пришить пайплайны, основной мотив у них один). Поэтому сегодня я попытаюсь привлечь ваше внимание к базовым командам dotnet`а.
Все изучать и делать будем сразу в докере. Создаем такой докерфайл:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build COPY . /app
Сдк и готового проекта нам хвтаит. Кстати, а на чем будем "тренироваться"? На простом консольном проекте с 1 нугет пакетом + работает в команде с еще одним проектом (типо для моделек), попробуем воссоздать "реальную" рабочую обстановку.

Выглядит это дело как то так. В первых 2 папках наши проекты.
Наше приложение:
using Extensions; using Newtonsoft.Json; namespace ConsoleApp1; class Program { static void Main(string[] args) { var model = new Model(){a = 2}; string json = JsonConvert.SerializeObject(model, Formatting.Indented); var deserializedModel = JsonConvert.DeserializeObject<Model>(json); Console.WriteLine($"{deserializedModel.a}"); } }
Теперь делаем докербилд и запускаем приложение.

Из интересного будет что-то вроде этого. Нужных нугетов, сборок и всего остального у нас нет. Начинаем с самого начала: устанавливаем нужные нугеты. делаем команду в папке Console.App1 для нашего основного приложения - dotnet restore ConsoleApp1.csproj. Получаем скаченный нугет пакет на машине:

Также появились доп файлы к проектам, но нас пока это не интересует.
Далее делаем dotnet build ConsoleApp1.csproj.

Появились нужные длл для нашего проекта! Осталось "опубликовать" наше приложение, поместив в папку, например main, всё что нужно нашему приложению.
Делаем dotnet publish ConsoleApp1.csproj -o /main (флаг -o говорит куда сложить все файлы, делаем это для удобства)

Появилась папка main и нужным нам длл файлик. Перейдем в папку и запустим проект dotnet ConsoleApp1.dll
# dotnet ConsoleApp1.dll 2
Ура! Приложение работает. Думаете как долго и нудно? На самом деле можно использовать только команду dotnet publish ConsoleApp1.csproj -o /main - под капотом будет билд + рестор. Но полную "последовательность" создания нашего основого dll файла нам знать нужно. Теперь научимся красиво писать докерфайлы с новыми знаниями:
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base WORKDIR /app FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ConsoleApp1/ConsoleApp1.csproj ConsoleApp1/ RUN dotnet restore "ConsoleApp1/ConsoleApp1.csproj" COPY . . WORKDIR /src/ConsoleApp1 RUN dotnet build ConsoleApp1.csproj --no-restore -c Release FROM build AS publish RUN dotnet publish ConsoleApp1.csproj --no-build -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "ConsoleApp1.dll"]
Что же тут происходит? Мы используем несколько этапов сборки aka многоступенчатую сборку, чтобы максимально уменшить итоговый контейнер. Сначала берем ТОЛЬКО рантайм, уже без всего нужного нам сдк. Далее в следующей сборке восстанавливаем зависимости, делаем это ДО копирования всех файлов, что позволит докеру нормально делать кеш слоя с зависимостями, этот закешированный слой будет использоваться, если файл ConsoleApp1.csproj не изменился, что происходит редко. После мы делаем билд с флагом -no--restore, чтобы исключить повторный рестор (это экономит время, позволяя на шаги разбить процесс сборки), далее ставим конфигурацию на релиз и делаем публикацию нашего приложения в папку publish. Возвращаясь к 1 образу, копируем нужную нам итоговую папку publish из предыдущего действия. Запускам наше приложение. Таким образом имеем в контейнере:

Только нужные файлы для нашего приложения, даже нет установленных нугет пакетов как это делается через dotnet restore. Только готовый длл в одной папке. Как удобно! И главное, все разбито по полочкам.
Теперь перейдем к ci/cd.
Тут сразу переходим к самому простому (и бессмысленному) пайплану:
name: .NET CI/CD Pipeline on: push: branches: - main pull_request: branches: - main jobs: build: name: Build & Test runs-on: ubuntu-latest steps: - name: Checkout репозитория uses: actions/checkout@v4 - name: Кэш NuGet пакетов uses: actions/cache@v3 with: path: ~/.nuget/packages key: nuget-${{ runner.os }}-${{ hashFiles('**/packages.lock.json') }} restore-keys: nuget-${{ runner.os }}- - name: Установка .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0' - name: Восстановление зависимостей run: dotnet restore ConsoleApp1/ConsoleApp1.csproj - name: Сборка проекта run: dotnet build ConsoleApp1/ConsoleApp1.csproj --configuration Release --no-restore - name: Паблиш проекта run: dotnet publish ConsoleApp1/ConsoleApp1.csproj --configuration Release --no-build -o pub - name: Сохранение артефактов сборки uses: actions/upload-artifact@v4 with: name: ConsoleApp1-artifact path: pub deploy: name: 🚀 Deploy needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: 📥 Загрузка артефактов uses: actions/download-artifact@v4 with: name: ConsoleApp1-artifact path: deploy/ - name: Просто хочу видеть 2 run: dotnet /home/runner/work/Rep/Rep/deploy/ConsoleApp1.dll
Получаем:

Более подробно этот файл можно разобрать с гпт. Но моменты, которые вы должны заметить: берем нугет пакеты в кеш (чтобы потом каждый раз не скачивали их), делаем также рестор, билд, паблиш, не забываем флаги. Сохраянем артефакт - просто готовыую папку с нужными файлами, её мы возьмём в другом джобе (логика, как у многоступенчатого докера). И в конце просто запустим приложение (не запутайтесь в папках, послежняя строка - пример, где лежит наш итоговый основной длл). Пайплайном это не назовёшь, но как базово обращаться со сборкой, вы узнали.
