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:
- Click here for some options on how to get Docker on Windows.
- Or if you’re on Windows 10 x64, you might want to go straight to this link, which will allow you to download an installer.
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.
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:
- Use the standard Microsoft .NET Core container from the Docker hub
- Copy our code from the local “/src/MyWebApi” folder to the “/app” folder in our container,
- Expose port 80 for the Web API traffic,
- Restore the necessary packages, and
- 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.
[ "value1", "value2" ]
But can we do better than this?
- 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:
containing all the necessary libraries for my project.
Step 2 – Modify our DockerFile to use optimised images
This time, we need our DockerFile to:
- Use the standard Microsoft ASP.NET Core container,
- Copy our code from the “/src/MyWebApi/bin/Debug/netcoreapp1.1/publish” folder to the “/app” folder in our container
- Expose port 80 for the Web API traffic, and
- 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.
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.