Dockerize your C# Application

Dockerize your C# Application

8 min read

Microsoft’s open source push

In late 2014, Scott Guthrie announced that Microsoft is open sourcing .NET. It took Microsoft a while, and change came in bits and pieces. But the end-result is many more open-source projects, some of them being built in the open.

The projects aren’t minor ones either; many are projects the open source community has embraced. Fast forward to 2017: we now have several ways to run C# and .NET code on Linux, in containers, using Docker, running at scale.

Why Do It?

Having written way more code in NodeJS and Go in the last 3 years, I know you’re thinking: “but why should I even care?”

There are 8 notable reasons:

  1. C# is a great language. It contains tons of development patterns, and includes async/await, parallel execution, generics, interoperability, lots of syntactic sugar and it’s actively developed.
  2. It has many smaller features like tuples, function multi-return value returns and amazing tooling.
  3. It has tons of open source packages, an active community and millions of developers out there.
  4. Breaks down monoliths running on Windows, and making pieces platform agnostic.
  5. Enterprises need to become more nimble. Containerization allows them to slowly break down the large, complicated code bases into smaller ones.
  6. Docker makes it easier to adopt micro-services. Engineers are now free to mix C#, .NET and other technologies on the same infrastructure.
  7. ‘Use the right tool for the right problem’ is no longer a slogan. With Docker and containers, it becomes reality.
  8. Enjoy all the Docker cultural and technological enablement. Technology independence, docker image re-use, multi-cloud deployments and portability, developer empowerment and much more.

How to Do it

Due to the history of .NET, not all variants of C# code can run the same way. Older .NET 4.5.x C# projects were heavily tied to Windows, and no Microsoft supported framework exists to run it on Linux. The newer .NET (Core) framework runs C# natively on Linux, but with a much smaller API surface provided by the .NET platform. There are 3 likely scenarios that are leading you to run C# code on Docker, each will require a slightly different approach:

  1. Move existing Windows C# workloads to Linux and Docker
  2. Start fresh with C# on Linux
  3. Containerize an existing ASP.NET Core or .NET Core app

Move Windows Workloads to Linux and Docker

Legacy enterprise solutions tend to use the full .NET 4.5.x framework. The stack is powerful, but built in a time when we had 1 huge program running on a huge machine.

It’s an all or nothing package of APIs as well. This approach was convenient when Internet speeds were slow, but made it difficult to port over the entire .NET framework to Linux as-is.

What that means, is that you can’t package a .NET 4.5.x C# app today and just expect it to run on Linux.

This is the toughest scenario of all, but there are solutions. Microsoft bought and now sponsors (but doesn’t support) an open-source project called Mono .

Mono is an open source, to the spec implementation of the .NET that runs cross platform.

It covers a good part of the .NET API surface, yet still requires you to port over pieces of your code base.

Making your app run on Mono, on Linux and Docker, entails work, with rewards.

Dockerizing and Running a Simple Mono C# App

Let’s learn how to dockerize a Mono C# app. We’ll download a sample web server I prepared, dockerize it and run it locally.

In a folder on your local machine, clone this github repo. Now run the following commands:

git clone https://github.com/AlaShiban/mono-nancysampleapp.git
cd mono-nancysampleapp
docker build -t learndocker/mono-sampleapp .
docker run -p 4321:4321 -t learndocker/mono-sampleapp

The first line clones a Nancy-based C# project from github. Nancy is a lightweight library used to create web APIs, like ASP.NET or Hapi on NodeJS.

Once in the folder of the code, we run two docker commands – one to build the image locally, the other to run it.

When the build finishes and you run the local docker image, you’ll see an output like this:

Running on http://0.0.0.0:4321

Opening that URL in a browser will reveal the C# code running and serving a simple web page that looks like this:

Let’s crack open the Dockerfile:

FROM mono
EXPOSE 4321
ADD . /src
WORKDIR /src
RUN mono nuget.exe restore
RUN xbuild /p:Configuration=Release
CMD [ "mono", "/src/Mono-MyFirstNancy/bin/Release/Mono-MyFirstNancy.exe" ]

Understanding the Mono Dockerfile

  1. Line 1 means that we’ll be using the latest official mono docker base image. This image gives us the mono runtime so we can run .NET .exe’s.
  2. Line 2 lets Docker know that the port we’re using inside the container is 4321
  3. Line 3 and 4 copy all the source and folder contents we’re currently running into a folder within the docker container. The folder is called /src
  4. Line 5 uses mono to run the nuget.exe package manager and re-acquires all missing NuGet packages. This is similar to how npm install works on NodeJS.
  5. Line 6 builds the project with the Release flag. It tells the compiler to run optimizations and avoid generating debug information. The output folder will be /bin/Release/ based on conventions.
  6. Line 7 defines the docker entry-point when someone runs our image. It will call mono and pass it the produced .exe name as a parameter. This runs the Nancy-based web server in a docker container.

That’s the barebones conceptual workflow you’ll have with Mono projects. Special sauce might be needed depending on how complex your original .NET 4.5.x C# project is.

Be patient and port diligently when it makes business and technological sense.

Start fresh with C# on Linux

The .NET API surface, runtime and C# compilation toolchain on Linux is called .NET Core. The main reason it was born was to accommodate growing forces within Microsoft to compete with popular and available web stacks on Linux.

Technologies like NodeJS, Ruby on Rails and others were gaining developer mindshare. Developers were also saying non-cross-platform development platforms were a non-starter. So the ASP.NET team built one.

Once other .NET teams jumped on the bandwagon, .NET Core was born. The community embraced it and ported many of the most popular packages to support it.

.NET Core and C# are now becoming a true contender on Linux.

Dockerizing and Running a .NET Core App

Let’s take a web app based on NancyFX, available on github, clone it, docker build it, then run it locally. Throughout this post, we’ll explain the Dockerfile line by line. The goal is for you to understand the anatomy of the why and how .NET Core apps run on docker within a Linux host.

First, clone the Nancy core-compatible project from github so you have the code locally:

git clone https://github.com/AlaShiban/dotnetcore-nancysampleapp
cd dotnetcore-nancysampleapp

Let’s now run the docker build command to have Docker create the image specified in the Dockerfile. This will run similar functions to the Mono scenario we described earlier:

  1. Restore nuget packages
  2. Compile C# source into runnable bits
  3. Define Docker execution entry point

Unlike Mono projects, .NET Core follows a model like nodejs. Youdotnet run

an application defined in a config file, much like a package.json and npm start in NodeJS.

Building the .NET Core App Docker Image

Let’s get the docker image built on our system by using the same build command we used before, labeling the image differently:

docker build -t learndocker/dotnetcore-sampleapp .

You’ll see the toolchain kick in and start transforming the source to runnable bits for the platform.

.NET is more like Java, where source compiles into an intermediate runnable language. In recent years, more and more bits can compile ahead of time to binary code as well.

That’s why when you choose a base-image for your .NET Core app, you should choose an image that has build tools and package managers in it to build correctly. Once you get comfortable with the toolchain, you can use the Docker multi-stage pattern to use runtime-only images for C# code thas you’ve previously compiled.

Let’s crack open the Dockerfile for this project and check out how we built the .NET Core app for Docker:

FROM microsoft/aspnetcore-build
EXPOSE 4321
WORKDIR /app
COPY . .
RUN dotnet restore
ENTRYPOINT ["dotnet", "run"]
  1. Line 1 gets not only the .NET Core framework, as you’d expect, but the ASP.NET Core package as well. That base-image contains many useful tools and libraries especially when developing web tools. For a Nancy web-server project, we wind up using some of the hosting libraries found in ASP.NET Core.
  2. Line 2 lets Docker know that the port we’re using inside the container is 4321
  3. Lines 3 and 4 are like the Mono project, where we copy the source files into the /app folder on the container.
  4. Line 5 is a .NET Core command that tells the package manager to download all the missing packages. This is like npm install for NodeJS apps.
  5. Line 6 describes what happens when the docker image is run. Docker will call dotnet run just like npm start scripts
  6. That’s the barebones way to get a .NET Core app running, even with some web components in there.

Containerizing an ASP.NET or .NET Core App

This one becomes much simpler now that we went over the previous section. Follow the same pattern of adding a Dockerfile to your ASP.NET Core or .NET Core app. Then, run the same commands as the samples above. Most of the time it will look like this:

FROM microsoft/aspnetcore-build
EXPOSE 4321
WORKDIR /app
COPY . .
RUN dotnet restore
ENTRYPOINT ["dotnet", "run"]

Replacing 4321 to the port you normally use while developing your app, like 80 or 8080.

Once you have a Dockerfile in place, you’ll build your first Docker image of your app like so:

docker build -t rockstardev/dotnetcore-mycoolapp .

Then run your built app:

docker run -p 4321:4321 -t rockstardev/dotnetcore-mycoolapp

Replacing 4321 with your favorite development port. Once you access the running container from a browser, you’ll get the same expected result:

Final Words

Choosing a completely containerized path is freeing. Bringing .NET Core and ASP.NET Core apps into the mix becomes a straightforward process. You don’t have to choose between OSes, tech stacks, or limiting libraries; you just build more micro-services.

When .NET Core is the tool of choice for certain tasks, follow this guide and others. You’ll find yourself enjoying C# 7, async/await, strong typing, generics and tons of libraries with powerful capabilities.

If you’re coming from the .NET world, consider that you can start expanding your horizons with new tech stacks along-side your .NET apps. Experience what makes NodeJS great and not-so-great compared to what you already know. It definitely won’t hurt your resume.

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Build your GitOps skills and credibility today with a GitOps Certification.

Get GitOps Certified

Ready to Get Started?
  • safer deployments
  • More frequent deployments
  • resilient deployments