Every now and then you’re presented with a scenario that should in theory work as advertised, but you’re not convinced until you actually see it with your own eyes. I recently found myself in this situation when migrating some code from the full .NET Framework running on a traditional Windows Server cluster to .NET Core running as a microservice on Kubernetes.
You can download the code for this post from my hello-netcore-wpf-nano GitHub repo. The Dockerfile for the Windows Desktop Nano Server container image on Docker Hub can be found in my dotnet-runtime-windowsdesktop GitHub repo.
The code in question is non-visual but has a dependency on WPF (Windows Presentation Foundation), in particular the Geometry classes in the System.Windows.Media
namespace. Because .NET Core version 3 and later includes WPF, and Nano Server (a compact version of the Windows operating system designed to run in containers) supports .NET Core (but not the full .NET Framework), it should be possible to run a non-visual WPF application on Nano Server in a Docker container — and to deploy it to Kubernetes using Amazon EKS, which has support for Windows containers.

.NET Core WPF Console App
The first step is to create a WPF console app using .NET Core, which is a rather unnatural act, since WPF is normally used to create a visual UI. You need to start out with a WPF .NET Core Library, using either Visual Studio or the .NET Core CLI.
dotnet new wpflib -n MyWpfConsoleApp
Running this command on Windows — don’t even think of doing it on a Mac — will produce a class library project based on the Windows Desktop SDK. To convert it to a .NET Core console app all you need to do is edit the csproj file and set the project output type to Exe.
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> | |
<PropertyGroup> | |
<TargetFramework>netcoreapp3.1</TargetFramework> | |
<UseWPF>true</UseWPF> | |
<OutputType>Exe</OutputType> | |
</PropertyGroup> | |
</Project> |
Then add a Progam.cs file with a static Main
method in which you use some Geometry classes.
using System; | |
using System.Windows.Media.Media3D; | |
namespace MyWpfConsoleApp | |
{ | |
public static class Program | |
{ | |
public static void Main() | |
{ | |
var point = new Point3D(1, 2, 3); | |
Console.WriteLine($"Point X: {point.X}, Y: {point.Y}, Z: {point.Z}"); | |
} | |
} | |
} |
Executing dotnet run
will produce the following output.
Point X: 1, Y: 2, Z: 3
Dockerized WPF Console App
To use Docker you must first install Docker Desktop for Windows.
To Dockerize your WPF console app, start by adding a Dockerfile to the project. If you are using Visual Studio, ordinarily you would right-click the project in the Solution Explorer and select Add Docker Support. But this won’t work with a WPF app.

A more feasible approach is to open the project folder in Visual Studio Code, where you have installed the Docker extension. Press Ctrl+P to open the command pallet, type Docker and select Add Docker files to Workspace.

Select .NET Core Console for Windows and you’ll get a Dockerfile that looks like this.
FROM mcr.microsoft.com/dotnet/core/runtime:3.1-nanoserver-1909 AS base | |
WORKDIR /app | |
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-nanoserver-1909 AS build | |
WORKDIR /src | |
COPY ["MyWpfConsoleApp.csproj", "./"] | |
RUN dotnet restore "./MyWpfConsoleApp.csproj" | |
COPY . . | |
WORKDIR "/src/." | |
RUN dotnet build "MyWpfConsoleApp.csproj" -c Release -o /app/build | |
FROM build AS publish | |
RUN dotnet publish "MyWpfConsoleApp.csproj" -c Release -o /app/publish | |
FROM base AS final | |
WORKDIR /app | |
COPY --from=publish /app/publish . | |
ENTRYPOINT ["dotnet", "MyWpfConsoleApp.dll"] |
Build and run the container.
docker build -t wpf-console-app .
docker run --rm --name wpf-console-app wpf-console-app
At this point you’ll get an error complaining that the Microsoft.WindowsDesktop.App framework could not be found.
It was not possible to find any compatible framework version
The framework 'Microsoft.WindowsDesktop.App', version '3.1.0' was not found.
You can resolve the problem by installing the specified framework and/or SDK.
This is because the Windows Desktop framework is not installed on the Nano Server base image. For that you’ll need to create a custom base image that installs the Windows Desktop framework on Nano Server (or simply use my Windows Desktop Nano Server container image). Here is the WinDesktop.Dockerfile I created. It downloads and runs the installer for the Windows Desktop runtime, then copies the dotnet folder from the installer image to the Nano Server runtime image.
# escape=` | |
# Installer image | |
FROM mcr.microsoft.com/windows/servercore:1909 AS installer | |
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] | |
# Retrieve .NET Core Runtime | |
# USER ContainerAdministrator | |
RUN $dotnet_version = '3.1.5'; ` | |
Invoke-WebRequest -OutFile dotnet-installer.exe https://download.visualstudio.microsoft.com/download/pr/86835fe4-93b5-4f4e-a7ad-c0b0532e407b/f4f2b1239f1203a05b9952028d54fc13/windowsdesktop-runtime-3.1.5-win-x64.exe; ` | |
$dotnet_sha512 = '5df17bd9fed94727ec5b151e1684bf9cdc6bfd3075f615ab546759ffca0679d23a35fcf7a8961ac014dd5a4ff0d22ef5f7434a072e23122d5c0415fcd4198831'; ` | |
if ((Get-FileHash dotnet-installer.exe -Algorithm sha512).Hash -ne $dotnet_sha512) { ` | |
Write-Host 'CHECKSUM VERIFICATION FAILED!'; ` | |
exit 1; ` | |
}; ` | |
` | |
./dotnet-installer.exe /S | |
# Runtime image | |
FROM mcr.microsoft.com/windows/nanoserver:1909 | |
ENV ` | |
# Enable detection of running in a container | |
DOTNET_RUNNING_IN_CONTAINER=true | |
# In order to set system PATH, ContainerAdministrator must be used | |
USER ContainerAdministrator | |
RUN setx /M PATH "%PATH%;C:\Program Files\dotnet" | |
USER ContainerUser | |
COPY --from=installer ["/Program Files/dotnet", "/Program Files/dotnet"] |
Note: If you create your own custom container image using this Dockerfile, you will need to push it to a container registry of your choice, such as Docker, Azure, Amazon or Google.
To get your local Dockerfile to work, replace the FROM statement at the top with one that specifies the custom Docker image you created (or you can reference my base image).
FROM tonysneed/dotnet-runtime-windowsdesktop:3.1-nanoserver-1909 AS base
After re-running the docker build
and docker run
commands shown above, you should see output from your WPF console app.
Congratulations. You now have a Nano Server container running a .NET Core app that references WPF.
Happy coding!