Docker Compose ASP.NET Core to Nano Image with Windows Container

In this article, I would like to demonstrate how to deploy an ASP.NET Core application with SQL Server dependency to nano server by docker. To support .NET Core in docker, Microsoft has published both of linux and windows server 2016 nano images in docker hub, you can reach them in dotnet repository. If this is your first time to be here, you will notice that there are two kinds of images:

  1. SDK image: The SDK image can be used as a build environment which has pre-installed all the required SDK tooling. You can either set volume to your host machine or copy the project into the image. In most condition, this is used in development or CI scenario. While, it is not suggested to be in production as it's bigger than necessary.
  2. Runtime image: The Runtime image is good for production since it relies on the .NET Core Runtime, not the larger .NET Core SDK.

If you are developing ASP.NET Core application, besides the above repository, it is suggested to use aspnetcore repository. Basically, the aspnetcore respository is built based on the dotnet repository and also together with some optimization such as the ASPNETCORE_URLS environment variable setting. In this article, I will take microsoft/aspnetcore:1.1.1-nanoserver(the version 1.1.1 runtime nano image) for example.

Prepare the APP

The demo app has dependency to SQL Server, and I will use microsoft/mssql-server-windows-express for that. Run the following code on host machine will generate the release version library pending for deployment. Certainly, you can use the publish functionality of Visual Studio for the same purpose.

 
dotnet restore
dotnet publish -c Release -o out

Prepare the Network

Before composing the two containers(app server and sql server) together, we need make sure they can be settled into same network. Windows containers function similarly to virtual machines in regards to networking. Each container has a virtual network adapter (vNIC) which is connected to a Hyper-V virtual switch (vSwitch). Windows supports five different networking drivers or modes which can be created through Docker: nat, overlay, transparent, l2bridge, and l2tunnel. Depending on your physical network infrastructure and single- vs multi-host networking requirements. If you don't specify the network when running the docker container, the default nat NIC will be chosen. You can review its subnet by the following

20175121

We can also create a new Nat network, while please make sure it is created under the default larger internal NAT networking prefix. In the following, I have created a new Nat network named "mybridge" and will use it soon later. For more network related in windows container, you may refer to https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-networking
20175122

Compose by IP

docker run command accepts ip as parameter to specify the ip address that the container will be assigned. For example, the following command has run the sql container with assigned ip 192.168.17.2 inside mybridge.

20175123

The docker file "Docker.nano" is as the following. As you can see, I have made the database connection string point to 192.168.17.2, thanks to ASP.NET Core's configuration system, we can easily make it without any modification to the app config itself.

 
FROM microsoft/aspnetcore:1.1.1-nanoserver
WORKDIR /app
COPY ./out ./
ENV ConnectionStrings:WebAppSampleContext Server=192.168.17.2;Database=WebAppSampleDb;Integrated Security=False; \
User ID=sa;Password=Password01!; \
Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False
ENTRYPOINT ["dotnet", "WebAppSample.dll"]

Then, run the below command on host machine to build and boot the app container, it should be able to connect to the sql container without problem.

 
 
docker build -t dotnetnano -f Dockerfile.nano .
docker run -d --network=mybridge --ip=192.168.17.3 -p 8000:80 dotnetnano

20175124

In order to verify whether everything is working fine, probably, you would like to run localhost:8000 on your host machine. Notice that, if you are using Linux image as your app container, it should work. While for nano image, currently there is a bug that affects how Windows 10 talks to Containers via "NAT" (Network Address Translation). Right now,  you must use the IP of the container directly, 192.168.17.3:80 in this demo to access the app.

Compose by Name

Use ip in connection string is not suggested and we are more comfortable to use name. Fortunately, docker run support name as parameter too. In the below command, I have name the sql container as "sqlexpress" which can be specified in the connection string.

20175125

Now, I can update the Docker.nano as the following.

 
FROM microsoft/aspnetcore:1.1.1-nanoserver
WORKDIR /app
COPY ./out ./
ENV ConnectionStrings:WebAppSampleContext Server=sqlexpress;Database=WebAppSampleDb;Integrated Security=False; \
User ID=sa;Password=Password01!; \
Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False
ENTRYPOINT ["dotnet", "WebAppSample.dll"]

Run the below command, ip will be automatically assigned to the app container. In order to retrieve the ip to verify the app, you can use "docker inspect" to get it.

 
docker build -t dotnetnano -f Dockerfile.nano .
docker run -d --network=mybridge  -p 8000:80 dotnetnano

By Compose File

docker-compose is an automatic way to boot the two containers without boot them one by one manually. What you need is a file docker-compose.nano.yml reside in the same directory with Dockerfile.nano shown below. It has defined the service name "sqlexpress" for sql container, and this can be used in the connection string. Also, mybridge is defined as the network.

 
version: '2.1'
services:
  sqlexpress:
    image: microsoft/mssql-server-windows-express
    environment:
      sa_password: "Password01!"
      ACCEPT_EULA: "Y"
    ports:
      - "1433:1433"
  webapp:
    build:
      context: .
      dockerfile: Dockerfile.nano
    depends_on:
      - "sqlexpress"
    ports:
      - "8000:80"
    environment:
      - "ConnectionStrings:WebAppSampleContext=Server=sqlexpress;Database=WebAppSampleDb;Integrated Security=False;User ID=sa;Password=Password01!;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
networks:
  default:
    external:
      name: mybridge

Run the below command on the host machine will build the app image and boot the two containers.

 
docker-compose -f docker-compose.nano.yml build
docker-compose -f docker-compose.nano.yml up

Notice that the depends_on option in the compose file is used to define the containers dependency and make sure they are started in dependency order. However, compose will not wait until a container is really ready to use before starting the next container, such as waiting the sql server service is booted successfully. Because of this, your real application could fail in startup phase because the sql server may not be ready yet. In order to solve this problem, it is suggested that the application can retry the connection instead of terminating directly.

Hope this article can give you an overview about how to deploy ASP.NET Core in docker, thanks!