Building .NET using GitLab CI/CD


As I often mention, I use .NET a lot in general, as it’s fairly easy to use, has a huge ecosystem, and has evolved really positively in the past years (long gone are the days of Mono :D).

Another component of this is that .NET projects are incredibly easy to build and publish using GitLab CI/CD.
Today, we’re gonna explore some ways of building and publishing a .NET project using just that.

Docker

Probably the most straightforward, considering a simple Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:9.0 AS buildCOPY . ./builddirWORKDIR /builddir/ARG ARCH=linux-x64RUN dotnet publish --runtime ${ARCH} --self-contained -o outputFROM mcr.microsoft.com/dotnet/runtime:9.0WORKDIR /appCOPY --from=build /builddir/output .RUN apt-get update && apt-get install -y --no-install-recommends \    curl \    && rm -rf /var/lib/apt/lists/*HEALTHCHECK CMD ["curl", "--insecure", "--fail", "--silent", "--show-error", "http://127.0.0.1:8080"]ENTRYPOINT ["dotnet", "MyApp.dll"]

Note: this assumes your app builds to MyApp.dll and has a healtheck endpoint on http://127.0.0.1:8080

Then building the container image itself is really easy:

stages:  - dockervariables:  DOCKER_DIND_IMAGE: "docker:24.0.7-dind"build:docker:  stage: docker  services:    - "$DOCKER_DIND_IMAGE"  image: "$DOCKER_DIND_IMAGE"  before_script:    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"  script:    - docker buildx create --use    - docker buildx build      --platform linux/amd64      --file Dockerfile      --tag "$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_NAME%+*}"      --provenance=false      --push      .    - docker buildx rm  only:    - branches    - tags

This will build, then publish the image to the GitLab container registry of the repo. It’s possible to also specify a different registry, but kinda useless as the default one is already excellent for most cases.

Regular build / NuGet build

This type of build just requires source itself without much additional configuration.

It will build the software, then either upload the resulting files as an artifact or publish it into the GitLab NuGet registry.

For those two, I can recommend setting up a cache policy like:

cache:  key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"  paths:    - "$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/project.assets.json"    - "$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/*.csproj.nuget.*"    - "$NUGET_PACKAGES_DIRECTORY"  policy: pull-push

And a small restore snippet:

.restore_nuget:  before_script:    - "dotnet restore --packages $NUGET_PACKAGES_DIRECTORY"

You can also directly specify the build image that you want to use at the top of the CI definition file with, for instance:

image: mcr.microsoft.com/dotnet/sdk:9.0

The regular build with artifact upload is also really easy:

build:  extends: .restore_nuget  stage: build  script:    - "dotnet publish --no-restore"  artifacts:    paths:      - MyApp/bin/Release/**/MyApp.dll

In this case, we use ** to avoid having to update the path every time we upgrade the .NET version (for instance, .NET 8 will put the build in the net8.0 directory, .NET 9 in net9.0, etc).

Now, we can also build and publish the solution to the NuGet registry:

deploy:  stage: deploy  only:     - tags  script:    - dotnet pack -c Release    - dotnet nuget add source "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text    - dotnet nuget push "MyApp/bin/Release/*.nupkg" --source gitlab  environment: $DEPLOY_ENVIRONMENT

As seen in this definition, this publish stage will only run on tag pushes, but it’s also possible to generate a version string with the current commit and pushing this as a nightly release.

As an additional step, but not really related to the build itself, I often activate the Secret, SAST and dependencies scanning as it can prevent really obvious mistakes. Doing so is also really trivial:

include:  - template: Jobs/Secret-Detection.gitlab-ci.yml  - template: Security/SAST.gitlab-ci.yml  - template: Security/Dependency-Scanning.gitlab-ci.yml

In the end, building .NET is extremely trivial.


Jae's Blog
Jae's Blog
@b@b.j4.lc

Jae’s blog, now federating properly!

130 posts
42 followers