CI/CD for .NET: MSBuild and dotnet CLI in Practice

Building and deploying .NET applications often turns into chaos: pipelines fail on restore, and releases are delayed due to manual operations. We set up CI/CD for .NET turnkey—from NuGet caching to Docker build—accelerating version releases and reducing routine. Our team handles the entire cycle: audit, pipeline configuration, and ongoing support, so you get stable and fast releases.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

CI/CD for .NET: MSBuild and dotnet CLI in Practice

Imagine: you're rolling out a new version of a .NET application, and the pipeline fails at dotnet restore because the agent has no cache — and you have to wait 5 minutes just to download dependencies. Or the whole team waits while Jenkins builds a legacy ASP.NET Framework project via msbuild.exe on a Windows agent, and then someone manually deploys to IIS. Sound familiar? We've encountered these situations many times: both with legacy .NET Framework and modern .NET (starting from version 6) using dotnet publish and multi-stage Docker builds. The stack differs — and so do the pipeline approaches. We set up CI/CD turnkey for any .NET project, accelerating releases and reducing manual operations. Contact us to assess your project and get a solution.

NuGet Package Caching for Different OSes

Without caching, dotnet restore on a clean agent pulls dependencies from scratch — 2–5 minutes just for restore. In GitHub Actions, caching packages is set up like this:

---
- uses: actions/cache@v4
  with:
    path: ~/.nuget/packages
    key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props') }}
    restore-keys: |
      ${{ runner.os }}-nuget-
---

For a self-hosted runner on Windows, the path is %USERPROFILE%\ .nuget\packages. If you don't account for this in the config, caching won't apply. With caching, restore takes seconds instead of minutes. NuGet cache is one of the key pipeline optimizations. Learn more about NuGet.

Why .NET Framework Builds Require a Windows Agent?

Windows-only dependencies: COM components, Windows Registry, legacy P/Invoke calls — such code cannot be built on a Linux agent. Solution: either keep a Windows agent for building, or refactor the code (not always possible). MSBuild on Windows via msbuild /p:Configuration=Release /p:Platform="Any CPU" is sometimes the only option. Setting up the agent for any dependencies is a standard task.

EF Core Migrations in CI

A typical mistake: migrations are applied directly to the database from the pipeline with dotnet ef database update without checking idempotency. On parallel staging deployments, two pods try to apply the same migration simultaneously — a race condition. Instead, generate an SQL migration script (dotnet ef migrations script --idempotent) and apply it through an init-container or a separate step with explicit locking. Microsoft Docs: EF Core migrations.

Signing Artifacts and Publishing NuGet Packages

If the project is a library, the pipeline should publish a package to Nexus or Azure Artifacts only on a release tag. Nuance: dotnet pack and dotnet nuget push require an API key, which must not be hardcoded. Use GitHub secrets or Vault, and a condition like if: startsWith(github.ref, 'refs/tags/').

GitHub Actions or GitLab CI for .NET?

Both tools are good, but GitHub Actions wins in integration with the .NET ecosystem: built-in caching, marketplace with actions for SonarQube and Codecov, simple YAML. GitLab CI requires its own runner but gives more control over instances. Cost-wise: GitHub Actions is free for public repositories; GitLab CI is free up to 400 minutes per month. In practice, we use GitHub Actions for .NET most often (75% of projects) because it requires less configuration.

Typical .NET Pipeline: Modern and Legacy

For Modern .NET (6+) on Linux Agent

---
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.x'
      - uses: actions/cache@v4
        with:
          path: ~/.nuget/packages
          key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
      - run: dotnet restore
      - run: dotnet build --no-restore -c Release
      - run: dotnet test --no-build -c Release --collect:"XPlat Code Coverage"
      - uses: codecov/codecov-action@v4
---

For .NET Framework on Windows Agent

- name: Build run: msbuild MyApp.sln /p:Configuration=Release /p:DeployOnBuild=true /p:PublishProfile=FolderProfile 

SonarQube in .NET Pipeline

A common mistake: running end without a successful begin due to a conditional step — analysis fails to upload. Always wrap in one job without skipping steps. Use dotnet-sonarscanner begin /k:"projectKey", then dotnet build, then dotnet-sonarscanner end.

Docker Image for ASP.NET Core

Multi-stage build reduces the final image from 1.5 GB to ~200 MB — 7.5 times smaller. This speeds up deployment and reduces attack surface. Example Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp/MyApp.csproj", "MyApp/"]
RUN dotnet restore
COPY . .
RUN dotnet publish "MyApp/MyApp.csproj" -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]

In CI: docker build, scan with Trivy (trivy image --exit-code 1 --severity HIGH,CRITICAL), push to registry with git sha tag. Microsoft recommends multi-stage builds for production images. Microsoft Docs: .NET Docker.

NuGet Caching Approaches Comparison

Approach Restore time Setup Reliability
No cache 5 min — —
GitHub Actions cache 30 sec 5 lines YAML High
Self-hosted cache proxy (Nexus) 10 sec 1 day admin Very high
Attachable cache (S3, MinIO) 20 sec 2-3 days Medium

What the Work Includes

  • Audit of current state: .NET version, project type, dependencies, deployment environment.
  • Pipeline setup: agent selection, NuGet cache, build, test, coverage, Docker build, push, deploy stages.
  • Integration of analyzers: SonarQube, Coverlet, security scanning (Trivy, Snyk).
  • Deployment to any environment: IIS, Kubernetes, Azure App Service, bare metal.
  • Documentation: variables, secrets, instructions for adding new projects.
  • Team training: how to use the pipeline and interpret results.

We guarantee pipeline stability: 99.9% SLO uptime, a runbook for every failure scenario. Contact us for an audit of your project.

Work Stages

  1. Audit current state: .NET version, project type (web/library/service), existing build scripts, dependencies (Windows-only or cross-platform), target deployment environment.
  2. Design pipeline: agent selection, cache configuration, stages: build → test → coverage → Docker build → push → deploy.
  3. Implement: write YAML, Dockerfile, scripts, integrate analyzers.
  4. Test: verify on staging, test parallel deployments, rollback.
  5. Deploy to production and provide documentation.

Time Estimates

Scenario Duration
.NET 6–8, Linux agent, Docker deploy 1–2 days
.NET Framework, Windows agent, IIS deploy 2–3 days
Monorepo with multiple projects 3–5 days
Integration SonarQube + EF migrations + Kubernetes Up to 5 days

Order turnkey CI/CD setup for .NET. Our engineers with years of experience will take your project from audit to deployment. Get a consultation — just contact us.