Building your first .NET Core container

Building your first .NET Core container

Visual Studio Tools for Docker

Using an updated Visual Studio 2017, you will be able to create a new project (.NET Core Web Application) and choose to enable Docker support. You may also be able to add Docker support to an existing .NET Core project. This will get you a working flow of hitting “F5” to run the app inside a container. Which is cool as a start. It does some neat stuff like build from inside the container, debug the app inside the container (have to share disk drives from Docker settings). But it doesn’t show how it was really done or how to customize it.

Creating the simplest container yourself

It all starts with a text file named “Dockerfile” without extension that you create inside the project folder with the following content. The name is standard and automatically identified by Docker and other editors.

FROM microsoft/dotnet:2.1-aspnetcore-runtimeWORKDIR /appCOPY . .ENTRYPOINT ["dotnet", "WebApplication1.dll"]

This file contains the instructions that Docker needs to build a docker image.

  1. FROM: means we will start from the microsoft/dotnet image with tag 2.1-aspnetcore-runtime which identify a specific major.minor version and also identify that it only contains the runtime not the SDK which is suitable for hosting a website not compiling. It also uses the default Linux OS which is “Debian Stretch” otherwise we will have to add something like jessie or alpine for other distribution or add nanoserver for Windows Nano. Check here for the other possible tags
  2. WORKDIR: means set the current directory inside the container to /app which is a standard website home directory (like wwwroot on IIS)
  3. COPY: means we need to copy files from our PC (. for current directory, the one that contains Dockerfile) to inside the container (another . for using the current workdir which is /app)
  4. ENTRYPOINT: tells Docker that the main point of executing this container is to call the dotnet command with argument WebApplication1.dll which is the name of our project output. Again this is assuming that it will exists in the same current directory /app

Now lets try to build this container. Since we are only hosting the app inside the container (not compiling it), then we need to build the project first then build the docker image from the compiled output. By following these steps:

  1. Create a new .NET Core Web Application from Visual Studio
  2. Create the file “Dockerfile” with the above content note that name of the dll should match project name.
  3. Change the Dockerfile properties, set “Copy to output directory” to “Copy always”.
  4. Publish the project, then navigate to the output directory (something like “bin\Debug\netcoreapp2.1\publish”) it should contain the project dll and the Dockerfile
  5. Open a command line window on this directory, and execute:

docker build . -t myapp

This will build a docker image from the current directory (using the file Dockerfile) and will tag the image as “myapp”

6. Check the image has been created by running:

docker images

7. Run the image using:

docker run -it -p 8000:80 myapp

This tells docker to:

  • Run the image named “myapp”
  • -it: Display its shell output in the command line
  • -p 8000:80 forward port 8080 from localhost to port 80 inside the container

If you see a message like “Now listening on: http//…:80” then we are successful and you can browse it from http://localhost:8000/

If you see a message like “Did you mean to run dotnet SDK commands? Please install dotnet SDK from” then the most probable cause is that dotnet command didn’t find the entrypoint dll, either because we wrote a wrong file name or because you are running docker from the source folder not the output folder which doesn’t have the dll on root

Exploring more options in Dockerfile

Now that you understand the basics, think of the possible things to do here. If you can copy files to the container and execute command lines. This means you can install any software as part of your deployment.

Here is an example taking a container image from Ubuntu and installing MongoDB. Source: KStaken

# Set the base image to Ubuntu
FROM ubuntu

# File Author / Maintainer
MAINTAINER Kimbro Staken

# BEGIN INSTALLATION
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
RUN echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | tee -a /etc/apt/sources.list.d/10gen.list
RUN apt-get update
RUN apt-get -y install apt-utils
RUN apt-get -y install mongodb-10gen

# RUN MAIN COMMAND
CMD ["/usr/bin/mongod", "--config", "/etc/mongodb.conf"]

Here are other list of Docker images/files that would give you more ideas and you can use in your .NET projects:

  • nginx: the web server
  • redis: the cache server
  • mongo: the nosql database
  • postgre: the sql database
  • registry: Docker registry that store your container images instead of public Docker Hub

Doing multistep build inside the Dockerfile

Now to a more advanced scenario, where you are in development and need to build a .NET Core project without installing dotnet sdk on your PC. Why is that needed? think of running a build agent that process builds for multiple teams/languages. We can construct this build agent so that it only has Docker installed that perform build for any language, given that it will use a container with SDK installed to build another container with only the runtime. This allow the build agent to process anything without having all the SDKs with all their versions installed on the agent machine.

So it’s called multi-step because it builds intermediate containers, move files between them to reach a final image and by default dispose of the intermediate containers. Here are the steps:

  1. Create a “base” container with just the runtime (this would be the final host eventually)
  2. Create another container from the dotnet sdk image (this one has the tools necessary to build and publish a website). Copy the source code folder into this container. Build the code. Name this container “build”
  3. Using the “build” container, run another dotnet command to publish the website into folder /app. And name this container “publish”
  4. Using the “base” image, copy folder /app from the “publish” container into folder /app in “base” image. Which gives us a final container named “final” with only the dotnet runtime and the published files (which is suitable to host in production) and get rid of everything else.

Here is the Dockerfile:

# Step 1: create a base container
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80

# Step 2: create a build container with dotnet sdk
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src
COPY WebApplication1/WebApplication1.csproj WebApplication1/
RUN dotnet restore WebApplication1/WebApplication1.csproj
COPY . .
WORKDIR /src/WebApplication1
RUN dotnet build WebApplication1.csproj -c Release -o /app

# Step 3: from the build container publish the website
FROM build AS publish
RUN dotnet publish WebApplication1.csproj -c Release -o /app

# Step 4: copy the output from publish container into base container
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "WebApplication1.dll"]

And this happens to be the Dockerfile that Visual Studio uses, when you enable Docker support for your project using the GUI.