Saturday, February 25, 2017

Using Docker for Asp.NET Core projects on Windows 10

Recently, I've been trying to get familiar with Docker containers for deployment scenarios, in both Go and .NET Core.  One thing I really wanted was an environment that that supported Docker in both Windows and Linux.  Fortunately, Windows 10 for Docker now supports this.

In order to use Docker for Windows, you must be running Windows 10 Professional.  This is required because Docker for Windows requires Hypervisor, which does not run in Windows 10 Home.

After the Docker for Windows installation, Docker will try to turn Hypervisor on for you, but note that even after upgrading, this might not work.  It did not for me.  Fortunately, I found a great document for resolving Hypervisor problems here at petri.com in case you have issues (one of the rare times StackOverflow didn't immediately answer my problem!).

So after I installed Docker into my windows 10 by following the steps documented at Docker site (they have done great job documenting, I think), I installed the Visual Studio 2017 RC, which has the latest .NET SDK tooling.  I then created a new NET Core Web API using the new project wizard, and checked the option for docker.  For reference, that option is circled below:



By selecting this option, the VS docker tooling automatically adds a Dockerfile and a docker-compose component to the project, which can be used to build and run the project in a docker container.   This is pretty cool, but one thing I don't like is that using the Visual Studio tooling obfuscates what is actually going on under the covers.

In order to get a better feel for what actually is going on in Docker, I switched to using command line to work with docker, and found a decent tutorial here at stormpath.com.  I still had some issues despite following this, and what follows is brief overview of how I got it all working.

First, in my project, I used the following Dockerfile, which I set to "Copy Always" for output option, so the Dockerfile is deployed as part of the "dotnet publish" command.

FROM microsoft/aspnetcore:1.0
COPY . /app
WORKDIR /app
EXPOSE 5000/tcp
ENV ASPNETCORE_ENVIRONMENT Development
ENV ASPNETCORE_URLS http://*:5000
ENTRYPOINT ["dotnet", "Holidays.dll"]

The first line FROM microsoft/aspnetcore:1.0 pulls in an optimized Linux runtime image for aspnetcore project.  The COPY command copies the current directory contents to app folder, and then WORKDIR is set to that /app folder.  I found some comments that using anything besides "/app" will not work, but didn't test.

EXPOSE is used to open up specific port from docker to the outside runtime environment.

The next two ENV lines are used to set runtime environment properties.  These are not required, but I included them as a form of documentation.  If you leave the URL entry out, the container will default to Production environment, and listen on port 80.   The URL entry should match the EXPOSE entry.  By overriding these I just made it clear where values are coming from.

Finally, the ENTRYPOINT starts up the dotnet process running in Kestrel web server, and launches the dll that you pass as second argument.  This should be the name of the dll created during compilation.

After setting up this docker file, then use the following steps at command line, at the root directory of the project you wish build create a container with.  I use Git Bash (some people prefer Powershell).

1. dotnet publish  
2. docker build bin/debug/netcoreapp1.0/publish -t holidays  (note: holidays is what the docker image is named).
3. docker run -d -p 80:5000 --name holidaytest holidays  (note: this creates container, and names is based on value after --name argument.  In this example holidaytest becomes the name of the container created from the image created in step 2, named holidays.  The -p 80:5000 maps the current machines port 80 to the internal port used in the container image set by dockerfile, which was 5000 in my example).

After these steps are complete, you can navigate to the URL for one of the web api controllers on localhost on your machine, and it will run.

To shut down, and then restart this container you can use the following:

docker stop holidaytest
docker start holidaytest

To get a list of images you have on your machine:

docker images

To get a list of containers on your machine, running or not, use:

docker ps -a

It's important to understand the difference between an image, which is just a basic recipe for the container, and the container itself, which is an instantiated version, with port mapping, etc.  I didn't quite get this for a while, and caused me some problems!