.net core, Docker

Deploying a .NET Core 1.1 Web API microservice to a Docker container in Windows

By now, everyone has heard about Docker, and the advantages of packaging microservices into their own containers (and if you haven’t, there’s a free e-Book from Microsoft here). I’ve wanted to investigate this technology and experiment for a while, and although Microsoft are presently investing heavily into the integration Docker and .NET, I’ve found it a bit difficult to find good practical documentation on how to get from a standing start to having a fully deployed .NET Core 1.1 Web API microservice to a container.

I’m going to write about how to get started with .NET Core and Docker, and hand-crank some of the files needed to spin up and deploy to a container. I’ll describe some things that tripped me up (along with how to solve the issues), and I’ll finish with a few optimisation techniques as well.

Visual Studio 2017 actually makes this all really easy – for more information on this, check out Donovan Brown during his section (starting at 1 hr 10min) of the Connect(); 2016 keynote.

Step 1: Install Docker on Windows

Docker already has some excellent documentation on how to get Docker on your Windows machine:

I won’t attempt to repeat these instructions – but installing Docker on Windows 10 is very straightforward. The links above give useful guidance on each step of running through the installer wizard, right up to the point where you can run your first container with the command below:

docker run hello-world

Step 2: Create a .NET Core 1.1 Web API microservice

I’ve written about how to create a Web API project targeting .NET Core v 1.1 previously here, so I won’t repeat myself – the ReSTful service created by the default Visual Studio template is good enough for the purposes of this tutorial.

You can obviously create a .NET Core 1.0 Web API microservice also, but I want to focus on the most up to date technology at the time of writing.

Step 3: Create a DockerFile at the root of the solution

The DockerFile is a series of instructions for Docker on how to deploy the application from a machine to the container. I like to create the DockerFile in the root of my solution as one of the “Solution Items” (since I see it as a solution concern, rather than a project concern). I’ve seen a lot of posts where the DockerFile is at the root of the Web application project (i.e. not the solution) – and that’s fine as well, I just prefer mine in the root folder.

screenshot-1479843720

One gotcha I’ve found is that Visual Studio 2015 wouldn’t allow me to create a DockerFile with file no extension as a solution item – it would only allow me to create a DockerFile.txt (note the file extension). I worked around this by creating an empty DockerFile at the root by:

  • Navigating to the solution root through PowerShell;
  • Run the command:
New-Item DockerFile -type file
  • Through Visual Studio, right click on the “Solution Items” folder, and choose to add an existing item (Shift+Alt+A);
  • Select “DockerFile”, and hit OK.

Now you can double click on the DockerFile to open it as a text file for editing in Visual Studio.

Step 4: Build up the DockerFile

As mentioned above, a DockerFile is a series of instructions for the Docker daemon to follow. In our simple case, we want to:

  1. Use the standard Microsoft .NET Core container from the Docker hub
  2. Copy our code from the local “/src/MyWebApi” folder to the “/app” folder in our container,
  3. Expose port 80 for the Web API traffic,
  4. Restore the necessary packages, and
  5. Run the dotnet application from within the container.

So we can translate this into the kind of code used in a DockerFile, which whould have the code below:

# Use the standard Microsoft .NET Core container
FROM microsoft/dotnet
 
# Copy our code from the "/src/MyWebApi" folder to the "/app" folder in our container
WORKDIR /app
COPY ./src/MyWebAPI .
 
# Expose port 80 for the Web API traffic
ENV ASPNETCORE_URLS http://+:80
EXPOSE 80
 
# Restore the necessary packages
RUN dotnet restore
 
# Build and run the dotnet application from within the container
ENTRYPOINT ["dotnet", "run"]

Step 5: Now build and run your Web API service in its own container

So now I can open PowerShell and run a couple of commands to build a Docker container and run it.

First to build the container:

docker build -t mywebapi .

And next I’ll run this container and use port 8080:

docker run -t -p 8080:80 mywebapi

When I run this, the console output is as follows:

Project app (.NETCoreApp,Version=v1.1) will be compiled because the version or bitness of the CLI changed since the last build
Compiling app for .NETCoreApp,Version=v1.1

Compilation succeeded.
    0 Warning(s)
    0 Error(s)

Time elapsed 00:00:02.0390916

Hosting environment: Production
Content root path: /app
Now listening on: http://+:80
Application started. Press Ctrl+C to shut down.
So I can now browse to http://localhost:8080/api/values/ and I receive the JSON output of:
[
  "value1",
  "value2"
]
which corresponds to what I’d expect to see as the default output for the default template’s GET request without a filter. I’m now running a Web API microservice in a container.

But can we do better than this?

This is a pretty straightforward example of how to create a Docker file and deploy some Web API code onto a container, restore packages, and then build and run the service. But there are a few areas where we can improve it:
  • Microsoft actually provide a dedicated ASP.NET Core Docker image – this contains a number of optimisations for size and speed (e.g. it contains a set of native images of ASP.NET Core libraries to help speed up the cold-start performance of the application).
  • Also, instead of compiling the application in the container, we can compile the application outside the container using the “dotnet publish” command, and then we can run the output DLL directly.

Step 1 – Run “dotnet publish” on your Web API project

So our first step is to publish our code – open PowerShell and navigate to the WebAPI root directory, and run the command “dotnet publish”. I found this created a folder at:

./src/MyWebAPI/bin/Debug/netcoreapp1.1/publish

containing all the necessary libraries for my project.

Step 2 – Modify our DockerFile to use optimised images

This time, we need our DockerFile to:

  1. Use the standard Microsoft ASP.NET Core container,
  2. Copy our code from the “/src/MyWebApi/bin/Debug/netcoreapp1.1/publish” folder to the “/app” folder in our container
  3. Expose port 80 for the Web API traffic, and
  4. Run the dotnet application against a DLL from within the container

This means our new DockerFile looks like:

# Use the standard Microsoft ASP.NET Core container
FROM microsoft/aspnetcore
 
# Copy our code from the "/src/MyWebApi/bin/Debug/netcoreapp1.1/publish" folder to the "/app" folder in our container
WORKDIR /app
COPY ./src/MyWebApi/bin/Debug/netcoreapp1.1/publish .
 
# Expose port 80 for the Web API traffic
ENV ASPNETCORE_URLS http://+:80
EXPOSE 80
 
# Run the dotnet application against a DLL from within the container
# Don't forget to publish your application or this won't work
ENTRYPOINT ["dotnet", "MyWebApi.dll"]

Step 3: Build and run our container

Again, we first build the container:

docker build -t mywebapi .

And next I’ll run this container and use port 8080 again:

docker run -t -p 8080:80 mywebapi

This time, the container is created a lot more quickly – we don’t spend a lot of time waiting for packages to be restored.

Also, the container starts a lot more quickly.

Finally, whereas the original container from Microsoft/dotnet was 678.8MB in size – this optimised container with Microsoft/aspnetcore is much smaller, 275.9MB.

Wrapping up

I’ve described how you can get started with Docker on Windows and deploy a Web API microservice to a Docker container – I’ve shown a couple of different ways to get this working, and a few optimisation techniques. Hopefully this is a useful introductory tutorial for anyone trying to get started with Docker on Windows and .NET Core – I’ll probably write more in the future about deploying .NET Core code to containers as I learn more about it.

Building Optimized Docker Images with ASP.NET Core