Dockerize .NET Framework Applications on GitHub Actions
If you're still using .NET Framework to maintain some older systems, you might run into issues with maintaining a pipeline for it.
Let's explore how to mitigate this by containerizing .NET Framework applications.
For the purpose of this blog post, I'll be using GitHub Actions, but your CI tool probably has equivalent commands to those you see here.
Docker has a good article on the what's and why's of containerization which you can go through to understand a bit more on the topic.
Note
.NET Framework doesn't work out of the box on Linux systems, we would need a Windows Runner for running them on Docker. Windows docker images will be slightly different from Linux docker images - syntax, capabilities, features etc.
The Application
You probably have an app you already want to dockerize, for the purpose of this blog I've created a sample github project. You can pull this repository or follow the remaining blog with your own codebase.
The Dockerfile
Base Image
To begin creating our Dockerfile, we need to get a base image which has the tools we need - of course another way to do this is to get a base image for the Windows OS and install the minimal tools required. We'll stick with Microsoft's official images for .NET which will get support & updates so we can take that burden away from ourselves.
There's an issue with syntax highlighting plugin I'm using. The code at the end of the Dockerfile section has a highlighted snippet.
1 FROM mcr.microsoft.com/dotnet/framework/sdk:4.8-20240709-windowsservercore-ltsc2019
Next, we need a few tools which will be used later to collect coverage and generate reports - you can skip this if it isn't needed.
3 RUN dotnet tool install --global altcover.global
4 RUN dotnet tool install --global dotnet-reportgenerator-globaltool
It must be puzzling as to why we have used the
dotnet
CLI on a .NET Framework project.These dependencies are not project specific in my demo app, and I just need the
.exe
files anywhere in the system and thedotnet
CLI comes preinstalled in the image we have. You can alternatively install a NuGet package for these locally to your project.
Build
To build the project we first need the solution files in the image & then we can use MSBuild
to create the dlls.
6 COPY . .
7 RUN nuget restore
8
9 # Build Project
10 RUN msbuild "./DockerApp/DockerApp.csproj" /p:Configuration=Release
Coverage & Testing
There are a few intricacies to testing & collecting coverage for .NET framework projects in the CLI - there are quite a few tools available and we chose to use AltCover
on top of the xUnit.net
console runner.
I have another post on coverage for .NET framework projects for a more detailed look into the process.
12 # Test & Collect Coverage
13 RUN msbuild "./DockerApp.Test/DockerApp.Test.csproj"
14 RUN AltCover.exe --inputDirectory=C:/DockerApp.Test/bin/Debug --localSource --assemblyFilter=DockerApp.Test --outputDirectory=__Instrumented
15 RUN AltCover.exe Runner --recorderDirectory __Instrumented --executable C:/packages/xunit.runner.console.2.9.0/tools/net472/xunit.console.exe -- ./__Instrumented/DockerApp.Test.dll
16
17 # Generate reports
18 RUN reportgenerator.exe -reports:coverage.xml -targetdir:reports
All together, it would look like this
FROM mcr.microsoft.com/dotnet/framework/sdk:4.8-20240709-windowsservercore-ltsc2019
RUN dotnet tool install --global altcover.global
RUN dotnet tool install --global dotnet-reportgenerator-globaltool
COPY . .
RUN nuget restore
# Build Project
RUN msbuild "./DockerApp/DockerApp.csproj" /p:Configuration=Release
# Test & Collect Coverage
RUN msbuild "./DockerApp.Test/DockerApp.Test.csproj"
RUN AltCover.exe --inputDirectory=C:/DockerApp.Test/bin/Debug --localSource --assemblyFilter=DockerApp.Test --outputDirectory=__Instrumented
RUN AltCover.exe Runner --recorderDirectory __Instrumented --executable C:/packages/xunit.runner.console.2.9.0/tools/net472/xunit.console.exe -- ./__Instrumented/DockerApp.Test.dll
# Generate reports
RUN reportgenerator.exe -reports:coverage.xml -targetdir:reports
The Pipeline
I've used GitHub actions as the easiest integration to showcase a sample project pipeline. You might be using something else like Jenkins, Azure Pipelines etc. - the flow of steps are going to remain same, the only difference would be the syntax on your choice of CI.
Stages
- Do a
docker build
to create the image - Create a container out of this image so that we can access any of the artifacts we need - coverage, dlls etc.
- Extract whatever artifacts you need out of the image, then cleanup
- Use your artifacts accordingly
I've left in an example of extracting and uploading an artifact (coverage) to GitHub actions. What you want to do with the coverage and dlls might be different - you might want to upload it to an S3 bucket, or host the coverage to a static site, or something else entirely.
name: dotnet-framework-docker-build
on:
push:
branches:
- "main"
jobs:
build-and-test:
runs-on: windows-2019
steps:
- uses: actions/checkout@v4
- name: Build & Test
run: |
docker build -f Dockerfile -t dockerapp .
- name: Extract Coverage Artifact
run: |
docker create --name dockerapp-container dockerapp
docker cp dockerapp-container:C:/reports/ c:/coverage/
- name: Cleanup docker daemon
if: always()
run: |
docker container prune -f
docker image prune -f
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage
path: c:/coverage/
With that, you would have successfully dockerized a system that needs to migrate to newer .NET Core versions ASAP!
Till the next blog post, Annyeong!