We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept
Card image cap

Dockerizing a Simple ASP.NET Core Application for Release Build

ASP.NET Core Docker  • Posted one month ago

Docker containers are a new way of deploying and rapid scaling of applications in a simple and most configurable manner. In the era of high availability and scalability requirements for applications, docker containers provide way of creating customized application environments with all necessary prerequisites in place. Similar to many application stacks which can be "dockerized" into containers, aspnetcore is also provided with necessary docker development and runtime "images" which are needed for creating containers.

What are Containers?

In a nutshell, a container is an encapsulated environment with necessary runtime and other configurations in place for applications to be able run independently as if running in a real machine. It is a virtual copy created from a real host machine, with necessary configurations in place facilitating easy extensibility.

In order to create a container image, we would require a "base image" which shall be used for putting in the application binaries along with settings about how to start and how to function.

For an aspnetcore application, building and deploying an application is done in below steps:

In the developer environment which we call the build environment:

  1. Restore packages for application, which generally downloads all the missing dependencies
  2. Publish the application onto an output directory which generates the release binaries
  3. Copy the binaries which are generated from the publish command onto the execution machine or the runtime environment

In the runtime environment where the application runs until stopped:

  1. Move to the path where the binaries are copied within the directory
  2. Run dotnet command on the binaries in the output directory to bootstrap the application

These steps are translated into docker language in terms of a Dockerfile, which is used to build containerized images for any application using a base image.

For dotnetcore, we are provided with two base images: one is an sdk image without any runtime environment. the second is a runtime environment without sdk. In order to publish and deploy an aspnetcore application we would make use of these two images to create two build "steps".

Let's begin by creating a Dockerfile (filename is Dockerfile without any extension) in the root of our application directory, which contains the below instructions to build - publish - run.


# Step 1 - The Build Environment #

#Base Image for Build - dotnetcore SDK Image

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /app

#Copy the csproj file first to restore required packages

COPY *.csproj .
COPY *.config .

RUN dotnet restore --disable-parallel --configfile ./nuget.config 

#Copy all the source code into the Build Container
COPY . .

# Run dotnet publish in the Build Container
# Generates output available in /app/out
# Since the current directory is /app

RUN dotnet publish -c Release -o out

# Step 1 Ends - The binaries are generated #

One can observe that we're pretty much using the same dotnet commands which are usually used to restore and publish an application.

Tip:

Sometimes during "dotnet restore" build step, we might encounter network issues which can result in error such as:

restore: Received an unexpected EOF or 0 bytes from the transport stream

which occurs when we use "dotnet restore" without any extra arguments. To fix this, we'd specify a nuget config file within the application root directory, like below:


<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="NuGet" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
</configuration>

data/Admin/2020/5/directory-aspnetcore.PNG

Additionally, we force the nuget to disable parallel downloads, by the flag --disable-parallel. These two extra parameters solve the error but can delay the restore process longer than usual.


dotnet restore --disable-parallel --configfile ./nuget.config

In the second build step, we fetch the binaries generated from previous step which is aliased as "build" denoted in the first instruction - FROM. This makes the docker runtime look for specified directory to copy from the "build" directory. Other steps are pretty much self explanatory.


# Step 2 - Continuing from the End of Step 1 #
# Second Stage - Pick an Image with only dotnetcore Runtime
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS runtime

# Set the Directory as /app
# All consecutive operations happen under /app
WORKDIR /app

# Copy the dlls generated under /app/out of the previous step
# With alias build onto the current directory
# Which is /app in runtime
COPY --from=build /app/out .

# Set the Entrypoint for the Container
# Entrypoint is for executables (such as exe, dll)
# Which cannot be overriden by run command
# or docker-compose
ENTRYPOINT ["dotnet", "OidcApp.dll"]

The ENTRYPOINT in this case is different from the CMD instruction we generally use in Dockerfile . An ENTRYPOINT indicates that the specified binary is an executable (dll, exe as such) and makes sure that this startup command can't be overriden in a docker run command.

Once we complete composing the Dockerfile, we have two ways to run it for creating the application container. One the usual docker build and run commands, which goes as below:


docker build . 

dot (.) specifies the location of the Dockerfile. Generally we run the command in the same directory where the Dockerfile is present.

data/Admin/2020/5/docker-build-aspnetcore.PNG

This builds a docker container for us. Then we run the created container as:


docker run -p 80:80 <container_id>

data/Admin/2020/5/docker-run-aspnetcore.PNG

The other way is by means of a docker-compose file. A Docker-Compose file is a yaml configuration file, which helps us in simplifying docker run command execution through a yaml script. In this script we specify all the parameters and configurations which otherwise need to be passed via the docker run command.


--- docker-compose.yaml ---

version: "3"
services: 
    webapp:
        build: .
        ports: 
            - 80:80

And to finish things up, we'd run the below command which builds and runs a container with our application running within.


> docker-compose up

data/Admin/2020/5/docker-compose-aspnetcore.PNG

Tip:

When we're running docker container with a port configuration, we might run into error as below:

"docker: Error response from daemon: Ports are not available: unable to list exposed ports: Get http://unix/forwards/list: open \\.\pipe\dockerBackendApiServer: The system cannot find the file specified"

It seems that this error occurs when we're not running the docker commands in an elevated prevliges. To solve this we can just run our commands in a Command Prompt with an administrator prevliges or a sudo user. Or we can have the docker desktop run in administrator prevliges.

Once we're all done, we can test our application by navigating to http://localhost:80 which is the port we have specified in our port configuration, that is translated by the docker to reach the port 80 within the container where the application runs.

data/Admin/2020/5/aspnetcore-app-run.PNG