Friday, September 21, 2018

Setting up a Dockerfile to publish a .net or .netcore application in a Windows container

Docker has been gaining significant traction lately, most of it can be credited to it's ability to create portable lightweight containers very quickly for continuous deployments and sprawling testing and staging environments. These environments would require a Dockerfile to carry a set of steps and instructions that would be needed to successfully restore, build and publish a .net or .netcore application.

Let's begin right away with the 1st step; selecting a build SDK image. For .net applications it would be microsoft/dotnet-framework:4.7.1-sdk and for .netcore applications we'll use microsoft/dotnet:2.1-sdk. Both of these are Windows images. The following would be the first step of our Dockerfile:

# Build SDK image
FROM microsoft/dotnet-framework:4.7.1-sdk AS build-env   # .net

FROM microsoft/dotnet:2.1-sdk AS build-env               # .netcore

We then copy only the csproj files, which will then be followed by the restore command. Docker has a very useful feature, it caches steps in intermediate images so that those steps are not executed for which there has been no change in files. Since project packages are least changed, restore would not be required every time. Copy of csproj files and restore are the next steps:

# Copy all csproj files
COPY Test.Repository/Test.Repository.csproj
COPY Test.Service/Test.Service.csproj
COPY Test.API/Test.API.csproj

# Restore dependencies for all projects
WORKDIR /app/Test.Repository
RUN dotnet restore
WORKDIR /app/Test.Service
RUN dotnet restore
RUN dotnet restore

Note: the steps are the same regardless of our application being .net or .netcore.

After restoring dependencies we copy all the remaining files and proceed with building all projects.

# Copy everything
COPY . ./

# Build all projects with Release configurations
WORKDIR /app/Test.Repository
RUN dotnet build -c Release
WORKDIR /app/Test.Service
RUN dotnet build -c Release
RUN dotnet build -c Release

Next step is a simple one, once all the projects successfully build and contain no errors we publish the startup project and save the build artifacts to a directory named "out".

# Publish startup project with Release configurations
RUN dotnet publish -c Release -o out

We successfully built and published the project but it is not ready for being started up and used. The current form cannot be written into a Docker image. The base SDK image is large in size (in GBs) and thus the build artifacts should be published on a runtime base image since they are comparatively small in size and are enough to run applications on. The following would be the Windows images we can use for each .net and .netcore applications:

# Build runtime image
FROM microsoft/dotnet-framework:4.7.1-runtime AS runtime-env   # .net

FROM microsoft/dotnet:2.1-runtime AS runtime-env               # .netcore

Once the runtime image is defined we will set the environment configurations to define what configurations from the applications are to be used by the image, and which port should be exposed:

# Select environment configurations
EXPOSE 5000/tcp

Now that all of the required steps are done, we only have to copy the build artifacts from the build image directory we specified before to the runtime image working directory and define an application entry point.

# Copy published artifacts to runtime image
COPY --from=build-env /app/Test.API/out .
ENTRYPOINT ["dotnet", "Test.API.dll"]

The only thing remaining now is to save this all by the name "Dockerfile" and test it out by opening up a command prompt, navigating to the directory the Dockerfile is saved in and executing the following command:

docker build -t testapi .

We can see each step being executed sequentially until a new image is created by the title testapi. Of course this is just one example of how a Dockerfile can be used and if there are any improvements suggested in the comment, I'll be happy to incorporate.