Getting Started With .NET Core Microservices Using Docker & VS Code on OSX
I’ve started working on some .NET Core microservices stuff and thought I’d outline the steps I took to get a basic instance of a .NET Core service up and running in Docker on OSX.
Install .NET Core
brew update
brew install openssl
mkdir -p /usr/local/lib
ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/
ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/
Install the .NET Core SDK from the page above.
I had an issue after the install where the dotnet command wasn’t exposed on my path. Found the following fix on one of the github issue comments:
ln -s /usr/local/share/dotnet/dotnet /usr/local/bin
Some comments suggest it may be because I use oh-my-zsh.
Once the sdk is setup successfully you can init a project:
mkdir dotnet-microservice
cd dotnet-microservice
dotnet new
dotnet restore
dotnet run
That should produce a simple Hello World
output:
Install VS Code
Now that we have the basic .NET Core app let’s install VS Code to use as our IDE. The OSX download is available at code.visualstudio.com
We need to add some nuget packages to the project so the first step is adding the Nuget package manager.
Go to View → Extensions and search for Nuget, the Docker extension is also useful for adding docker commands to VS Code.
Once you have Nuget installed you can access Nuget commands in the command bar with F1:
I added Kestrel and a couple of MVC nuget packages, you can see the list in the project.json
file further down.
Once you add a nuget package through the UI you will get prompted to restore (dotnet restore
is the terminal command):
Some Code
A microservice will normally expose a RESTful interface, to serve as the host we add a WebHostBuilder
to the main method. Kestrel is a lightweight web server included in .NET Core. The Startup
class is passed to the web host and allows you to define routing, DI and other services. The ConfigurationBuilder
can be used to compile config settings from different sources; file, args, env, etc.
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var config = new ConfigurationBuilder()
.Build();
var builder = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConfiguration(config)
.UseStartup<Startup>()
.UseKestrel()
.UseUrls("http://localhost:5000");
var host = builder.Build();
host.Run();
}
The Startup class here is pretty basic, I just went with the default routing provided out of the box by MVC. You can add different route patterns or you can use attribute based routing if desired.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore();
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.UseMvcWithDefaultRoute();
}
}
The controller is a simple math function. Um, multiplication…
public class CalculatorController : Controller
{
public string Multiply(int first, int second)
{
return (first * second).ToString();
}
}
The project.json
lists the dependancies. In the most recent version of .NET Core, these .json
files get upgraded to .csproj
files
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.AspNetCore.Routing": "1.1.0",
"Microsoft.AspNetCore.Mvc.Core": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.1.0"
},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
},
"imports": "dnxcore50"
}
}
}
Using dotnet run
you can start the server:
The debug mode of VS Code lets you debug the application as you would expect:
Docker
Now that we can run the service locally, lets add it to a docker container. The first step is to create a dockerfile
. The Docker extension for VS Code has some commands built in:
This was the first version of my dockerfile
, I’ll walk through some of the issues I had to troubleshoot further down.
FROM microsoft/aspnetcore:1.0.1
LABEL Name=dotnet-microservice Version=0.0.1
ARG source=.
WORKDIR /app
EXPOSE 5000
COPY $source .
ENTRYPOINT dotnet dotnet-microservice.dll
Building my docker file from VS Code:
docker build -f Dockerfile -t dotnet-microservice:latest .
Sending build context to Docker daemon 518.1 kB
Step 1/7 : FROM microsoft/aspnetcore:1.0.1
1.0.1: Pulling from microsoft/aspnetcore
Base image vs app version mismatch
Running the docker container:
▶ docker run -it --rm dotnet-microservice:latest
The specified framework 'Microsoft.NETCore.App', version '1.1.0' was not found.
- Check application dependencies and target a framework version installed at:
/usr/share/dotnet/shared/Microsoft.NETCore.App
- The following versions are installed:
1.0.1
- Alternatively, install the framework version '1.1.0'.
My first issue was the incorrect version of my app compared to the aspnetcore version I pulled down in my base image.
I updated the base image in the dockerfile and rebuilt:
FROM microsoft/aspnetcore:1.1.0
Copying the correct folder
▶ docker run -it --rm dotnet-microservice:latest
Did you mean to run dotnet SDK commands? Please install dotnet SDK from:
http://go.microsoft.com/fwlink/?LinkID=798306&clcid=0x409
The issue this time turned out to be the COPY
command wasn’t copying my build output to the working dir of the image.
COPY $source/bin/Debug/netcoreapp1.1/ .
Publishing artifacts
docker run -it --rm dotnet-microservice:latest
Error: assembly specified in the dependencies manifest was not found -- package:
'Microsoft.DotNet.PlatformAbstractions', version: '1.1.0', path: 'lib/netstandard
1.3/Microsoft.DotNet.PlatformAbstractions.dll'
This time I realized I was copying the build
output but I needed to be running a publish
so that dependencies are included. I needed to run dotnet publish
and change the COPY
to pick up the published output.
▶ dotnet publish
Publishing dotnet-microservice for .NETCoreApp,Version=v1.1
Project dotnet-microservice (.NETCoreApp,Version=v1.1) was previously compiled. S
kipping compilation.
publish: Published to /Users/danny/dev/dotnet-microservice/bin/Debug/netcoreapp1.
1/publish
Published 1/1 projects successfully
COPY $source/bin/Debug/netcoreapp1.1/publish .
Port forwarding
The publish
did the trick, now I was able to run the docker container… but not access the endpoint.
▶ docker run -it --rm dotnet-microservice:latest
Hello World!
Hosting environment: Production
Content root path: /app
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
I ran the docker container specifying *
as the host to bind to and forwarding port 5000 on the container to post 5001 on my machine.
▶ docker run -p 5001:5000 dotnet-microservice
Hello World!
Hosting environment: Production
Content root path: /app
Now listening on: http://*:5000
Application started. Press Ctrl+C to shut down.
Curl confirms that there is nothing running on port 5000 and the container is exposed on port 5001.
The docker ps
command lists the running containers
That’s it, a simple microservice running on the .NET Core Kestrel server and packaged up in a docker image. It’s a basic structure for somethign that can be built upon in future.
I’ve included the terminal commands/output just in case others hit similar issues and hopefully the solutions above will help them.