.NET Core with Docker Containers

Dockerizing your .NET Core application

Let’s create a production build with Docker

Andre Lopes
The Startup

--

Photo by Ishant Mishra on Unsplash

Hey guys!

Today I’m writing about creating a production build for a .NET Core application using Docker. These are super exciting technologies and see many people wanting to learn more about how to use them together.

For those that don’t know, Docker allows you to easily create, deploy, and run applications. You pack your app and all its dependencies together and then ship into a docker image. Then you can run a container with this image in any machine running docker and be assured that your application will work fine, regardless of the machine’s configurations.

Requirements

Docker Windows requires Hyper-V to run and it is only shipped with the Pro version, so you are on a Windows that is not a Pro version you might have to use Docker Toolbox.

You might want to add this Docker extension that adds syntax highlighting and other features to docker in VS Code

Set sail!

Now that we have our environment set, we can start by initializing a new .NET app. For this example, I took the liberty to choose a Blazor Server app, just for fun. :)

Let’s create our application with:

dotnet new blazorserver -o DockerDotnetApp
App folder structure

Awesome! Now we have a .NET app ready.

We can test it by running

dotnet run

When you navigate to https://localhot:5001 you should see this:

Blazor Server App initial page

Yay! It works!

Let’s Docker it!

Now that we have a working app we can head to docker!

First, add a new file name Dockerfile (without extension) to the solution folder.

Dockerfile

Now we can open and start configuring it!

Dockerfile is a set of instructions so docker can know what it needs and what it needs to do to build a proper image for your application.

The basic instructions for a Dockerfile are:

  • FROM — It is gonna tell Docker which is the base image to be used
  • WORKDIR — Set the working directory inside our Docker image
  • COPY — Copy files from the context directory to our Docker image
  • RUN — Run some command inside our Docker image in the build step
  • CMD — Sets the command to be run when running our Docker image
  • ENTRYPOINT — It is similar to CMD with a difference that is harder to override its initial command while starting your container

First, we need to specify which image we are going to use as a base. This base image is a simple Linux OS that contains the minimum required to build our app. You can find all Microsoft’s .NET images here.

The images we are going to use first is for the .NET Core SDK: mcr.microsoft.com/dotnet/core/sdk:3.1.301-alpine. So let’s tell that in our Dockerfile with:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1.301-alpine AS build-env

The AS build part just tags this build part with the name build-env. Internally it creates a temporary container to be used later.

Then we set our work directory to be /app so we don’t end up using the root folder for our app:

WORKDIR /app

Now that we have this set, we can copy our solution and project files to the container.

The reason why we copy them first is that when you build an image, Docker automatically caches this step so if you need to rebuild the image, it doesn’t need to execute everything again. This only happens if nothing changed in this step or previous steps before it. So, as we these steps don’t change too often and we don’t want to download everything again, because it will cost time, we make sure to put it right at the beginning of our Dockerfile.

To do so, we can run the following step:

COPY *.csproj ./

Now we tell docker to run dotnet restore

RUN dotnet restore

After that we copy everything else to our image with:

COPY . ./

Now we just need to Publish our .NET app with

RUN dotnet publish -c Release -o out

It tells us to publish our app in Release mode and store it in the out folder.

With that now we can go to the that is gonna build our final image.

For it, we are gonna use the runtime image because we will just run our app. and set the working directory to /app .

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1.5-alpine
WORKDIR /app

Then we need to copy everything from the previously published app to this new container:

COPY --from=build-env /app/out .

Notice that here we use --from=build-env to tell docker to get these files from the temporary container.

With that now we just need to tell docker to expose the port 80 (because it is the port that .NET uses in debug mode) and tell docker to run it when the container starts:

EXPOSE 80
ENTRYPOINT ["dotnet", "DockerDotnetApp.dll"]

Your Dockerfile should be like this:

Now let’s build our image using the command line. The command to build it is:

docker build -t APP_TAG APP_CONTEXT
  • -t is the switch to tag our image. It adds a name to the image.
  • APP_TAG is the tag
  • APP_CONTEXT is the folder that contains your Dockerfile, basically

So, in the end, we can run it like this:

docker build -t andrevitorlopes/dotnetapp .

By convention, tags should start with your Docker username followed by your docker image name, for example andrevitorlopes/dotnetapp.

Docker image build process

You can see all the built images with

docker image list
Local images list

Now let’s run our container:

docker run andrevitorlopes/dotnetapp

You should see something like this in the console:

Which means that the Docker container is running.

Now type CTRL + C to exit the container. (Don’t worry, your container will continue running on the background)

If you run docker ps you can see your container running like:

Running containers

But you will not be able to connect to your app just yet.

The port we exposed with the Dockerfile is just the container port, we need to map it to one of our local machine ports to be able to connect to it.

First, kill the container with:

docker kill CONTAINER_ID

You can find the container image in docker ps . Mine here is bd8eab8dd1cb.

docker kill bd8eab8dd1cb

Then we need to run docker with the port switch that is gonna map the container port to our local port -p LOCAL_PORT:CONTAINER_PORT

docker run -p 8080:80 andrevitorlopes/dotnetapp

Now if you go to http://localhost:8080 in your browser, you should see your application running.

Conclusion

Docker is such a powerful tool to generate application deployment builds. The concepts of images and containers make it super helpful and simple when dealing with deployment to multiple environments and you can be assured that you won’t have issues about differences between environment infrastructures and configurations.

So you saw how it is simple to create this .NET Core application production build using Docker and how you can see it running on your local machine.

What’s next?

Next, it is very useful to know how to use docker-compose for development build, it is a tool that allows you to easily run and integrate multiple containers.

Also, you can surely dive into Kubernetes, the next generation of application deployment. It is an orchestrator that helps you easily escalate your application when needed, among other great features.

--

--

Andre Lopes
The Startup

Full-stack developer | Casual gamer | Clean Architecture passionate