Composing your own Windows Container with a Dockerfile

In the previous blog post Getting Started with Windows Containers I showed how to run containers and pull existing containers from the public repository Docker Hub. Now it is time to create a new image and add it to Docker Hub.

I wanted to create a Hello World image that works on Windows Containers. I chose to create the Hello-World program using .NET Core – it runs cross platform (Linux, Mac & Windows), but I’m using Windows.

On the Windows Server 2016 container host I created in the previous blog post, I needed to install .NET Core. You can either go to to download or execute the following PowerShell to download the installer.

Invoke-WebRequest -OutFile c:\dotnetinstall.exe

I created a folder called HelloWorld and executed the following commands in a command prompt:

PS C:\Users\aly\Desktop\HelloWorld> dotnet new
Created new C# project in C:\Users\aly\Desktop\HelloWorld.
PS C:\Users\aly\Desktop\HelloWorld> dotnet restore
log : Restoring packages for C:\Users\aly\Desktop\HelloWorld\project.json...
log : Writing lock file to disk. Path: C:\Users\aly\Desktop\HelloWorld\project.lock.json
log : C:\Users\aly\Desktop\HelloWorld\project.json
log : Restore completed in 1049ms.
PS C:\Users\aly\Desktop\HelloWorld> dotnet run
Project HelloWorld (.NETCoreApp,Version=v1.0) will be compiled because expected outputs are missing
Compiling HelloWorld for .NETCoreApp,Version=v1.0

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

Time elapsed 00:00:01.8161040

Hello World!
PS C:\Users\aly\Desktop\HelloWorld> dotnet publish
Publishing HelloWorld for .NETCoreApp,Version=v1.0
Project HelloWorld (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
publish: Published to C:\Users\aly\Desktop\HelloWorld\bin\Debug\netcoreapp1.0\publish
Published 1/1 projects successfully
PS C:\Users\aly\Desktop\HelloWorld>

You can use any executable, I just needed something simple to work with. .NET Core is my simple choice.
I am going to base my image on the microsoft/windowsservercore image. But instead of installing .NET Core myself, I can use an image that already has .NET Core installed called microsoft/dotnet:windowsservercore. See all the official Microsoft Docker images at the Microsoft Docker Hub repository. So pull down the microsoft/dotnet:windowsservercore image

docker pull microsoft/dotnet:windowsservercore

If you want to play with it run the following command to start a container and a command prompt will appear. Type exit when you want to go back to the container host.

docker run -it microsoft/dotnet:windowsservercore

Now let’s create the Hello World image. Create a file called Dockerfile without extension outside the /HelloWorld folder and add this:

FROM microsoft/dotnet:windowsservercore
MAINTAINER Anders Lybecker (@Lybecker)
ADD /helloworld/bin/Debug/netcoreapp1.0/publish/ c:\\code
ENTRYPOINT dotnet c:\\code\\helloworld.dll

Line 1 dictates that I’m basing the Hello World image on the official image microsoft/dotnet:windowsservercore
Line 2 is just maintainer information and is optional
Line 3 adds the content of the publish folder of the container host to the image in c:\code
Line 4 sets the default command to execute the Hello World program

For more details see the Dockerfile reference.

Build the container by executing the docker build command. The image is named myhelloword and tagged with v1 for version 1. The last . specifies where the Dockerfile is located.

PS C:\Users\aly\Desktop> docker build -t myhelloworld:v1 .
Sending build context to Docker daemon 341.5 kB
Step 1 : FROM microsoft/dotnet:windowsservercore
---> 1e21a0790e96
Step 2 : MAINTAINER Anders Lybecker (@Lybecker)
---> Running in 6812a0ecf67d
---> 5dce994e32a4
Removing intermediate container 6812a0ecf67d
Step 3 : ADD /helloworld/bin/Debug/netcoreapp1.0/publish/ c:\\code
---> 22fade758f23
Removing intermediate container 665f9e677611
Step 4 : ENTRYPOINT dotnet c:\\code\\helloworld.dll
---> Running in dad461888c4c
---> 204ed6ed0b59
Removing intermediate container dad461888c4c
Successfully built 204ed6ed0b59

That’s it. You have now created your own image. Let’s try to run it

PS C:\Users\aly\Desktop> docker run myhelloworld:v1
Hello World!

You can see the image in the local image repository with the docker images command.

PS C:\Users\aly\Desktop> docker images
REPOSITORY                  TAG               IMAGE ID     CREATED        SIZE
myhelloworld                v1                204ed6ed0b59 10 minutes ago 8.111 GB
microsoft/dotnet            windowsservercore 1e21a0790e96 2 weeks ago    8.111 GB
microsoft/windowsservercore 10.0.14300.1030   02cb7f65d61b 10 weeks ago   7.764 GB
microsoft/windowsservercore latest            02cb7f65d61b 10 weeks ago   7.764 GB

The myhelloworld:v1 image is based on the microsoft/dotnet:windowsservercore which is based om microsoft/windowsservercore:10.0.14300.1030. You can see all the layers via the docker history command

PS C:\Users\aly\Desktop> docker history myhelloworld:v1
204ed6ed0b59 12 minutes ago cmd /S /C #(nop) ENTRYPOINT ["cmd" "/S" "/C" 46.58 kB
22fade758f23 12 minutes ago cmd /S /C #(nop) ADD dir:4bef0fa9bcfacdaa9bb8 40.96 kB
5dce994e32a4 12 minutes ago cmd /S /C #(nop) MAINTAINER Anders Lybecker 181.2 MB
1e21a0790e96 2 weeks ago cmd /S /C mkdir warmup && cd warmup & 40.96 kB
2 weeks ago cmd /S /C #(nop) ENV NUGET_XMLDOC_MODE=skip 4.756 MB
2 weeks ago cmd /S /C setx /M PATH "%PATH%;%ProgramFiles% 160.7 MB
2 weeks ago cmd /S /C powershell -NoProfile -Command 40.96 kB
2 weeks ago cmd /S /C #(nop) ENV DOTNET_SDK_DOWNLOAD_URL 40.96 kB
2 weeks ago cmd /S /C #(nop) ENV DOTNET_SDK_VERSION=1.0. 7.764 GB

I have made my Hello World image available on Docker Hub, so you can pull and run the image on your Windows Container host like so:

PS C:\Users\aly\Desktop> docker pull anderslybecker/dotnet-hello-world:windowsservercore
windowsservercore: Pulling from anderslybecker/dotnet-hello-world
Digest: sha256:1df17a8a38d969b71b38333be25f76757e69f1537c7a86a6ee966bca87163464
Status: Image is up to date for anderslybecker/dotnet-hello-world:windowsservercore
PS C:\Users\aly\Desktop> docker run anderslybecker/dotnet-hello-world:windowsservercore
Hello World!

Getting started with Windows Containers

Soon Windows Server 2016 will be released and so will the Docker Engine compatible Windows Containers feature. Here is a tutorial for you to get started with Windows Containers.

One thing to be aware of when working with containers is that the underlying host must be of the same type of operating system as the container running on the host. Linux containers on Linux hosts and Windows containers on Windows hosts.

First a container host is needed – you can use Windows 10 Anniversary Update or a Windows Server 2016.

The easiest way of getting started is to spin up a Windows Server 2016 on Azure (get a free trial) with the Container feature enabled.

  1. Select new
  2. Type “container” to filter only for container images
  3. Select the “Windows Server 2016 with Containers” equivalent (as of writing Tech Preview 5)CreateWindowsServer2016Container
  4. Follow the wizard to specify VM size etc.

Alternatively, you can follow Windows Containers on Windows Server guide to install the Container feature on an existing Windows Server 2016.

Once you have created the host you can connect to the host via RDP (in the Azure portal use the ”Connect” button in the top menu).

Start up a command prompt or the PowerShell. You can use PowerShell for Docker or the Docker CLI to execute Docker commands. The commands are the same across platform – no matter if you are using Linux or Windows-based containers. I’ll be using the Docker CLI commands. The common commands are:

  • Docker info – which will show you version etc.
  • Docker images – shows all the images in the local repository
  • Docker ps – show all the running containers
  • Docker ps -a – the -a (or -all) shows all containers including containers that are no longer running
  • Docker run – run an instance of an image

Let’s get started.

Run the following command to see which images are available in the local repository.

PS C:\Users\aly\Desktop> docker images
REPOSITORY                  TAG             IMAGE ID     CREATED      SIZE
microsoft/windowsservercore 10.0.14300.1030 02cb7f65d61b 10 weeks ago 7.764 GB
microsoft/windowsservercore latest          02cb7f65d61b 10 weeks ago 7.764 GB
PS C:\Users\aly\Desktop>

On my Windows Server 2016 Tech Preview 5 there are two images both with the name microsoft/windowsservercore with two different tags. Both of them has the same image id, so they are the same image with two different tags.

To start a container of the image tagged with ‘latest’ run the following:

PS C:\Users\aly\Desktop> docker run microsoft/windowsservercore:latest
Microsoft Windows [Version 10.0.14300]
(c) 2016 Microsoft Corporation. All rights reserved.

PS C:\Users\aly\Desktop>

The tag is optional, but the default value is ‘latest’.

The container was started and a command prompt appeared, but then it shut down again and it returned to my PowerShell prompt.

If you want to interact with the container, add the -it (interactive) option. You also have the option of specifying which process should be run in the container (cmd is default for this image):

docker run -it microsoft/windowsservercore:latest cmd

Now a command prompt appears and you are in the context of the container. If you modify a file e.g. adds or deletes a file, then the changes will only apply to the container and not the host.

Create a simple file like this:

echo "Hello Windows Containers" > hello.txt

You can exit the container by typing exit, and the container will terminate. Alternatively, you can press CTRL + P + Q to exit and leave the container running.
If you left the container running, you can see the container by listing the Docker processes:

PS C:\Users\aly\Desktop> docker ps
CONTAINER ID IMAGE                             COMMAND   CREATED       STATUS    PORTS NAMES
23ca16bb6fdb microsoft/windowsservercore:latest "cmd" 4 minutes ago Up 4 minutes pedantic_lamport

If the container was terminated, the -a option needs to be appended.
You can reattach to the container by specifying the container id or name. In my case 23ca16bb6fdb or pedantic_lamport like so:

Docker attach 23ca16bb6fdb

You only have the Windows Server Core image in the local repository, but you can download others by pulling from Docker Hub.

docker pull microsoft/nanoserver

Remember that only Windows-based images will run on a Windows host, so if you try the Hello-World Linux-based image, it will fail with a not so elaborate error message.

PS C:\Users\aly\Desktop> docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
c04b14da8d14: Extracting [==================================================>] 974 B/974 B
failed to register layer: re-exec error: exit status 1: output: ProcessBaseLayer C:\ProgramData\docker\winc266a137b0b1fffedf91d8cd6fcb6560f12afe5277e44bca8cb34ec530286: The system cannot find the path specified.

For now it is not easy to differentiate between Linux or Windows-based images on Docker Hub. I would have wished for a filter, making it easier to find relevant images.

Microsoft has a public repository of all the official released Microsoft container images.

.NET compiler platform (Roslyn) analyzer packages

The greatest new feature in Visual Studio 2015 is the .NET compiler platform previously known as Roslyn. The .NET Compiler Platform is an open source compiler for C# and VB with rich code analysis APIs. It enables developers to build code analysis tool like code analyzers, fixes and refactorings.

The community has built a number of packages containing great analyzers, fixes and refactorings. These can be installed either as a Visual Studio 2015 Extension or at project level as NuGet packages.

Refactoring Essentials

Refactoring Essentials contains approx. 200 code analyzers, fixes and refactorings
Simple defensive code analyzers like parameter checking.


Simplyfing code by converting conditional ternary to null coalescing.


CSharp Essentials

CSharp Essentials focuses on the new features in C# 6 such as the nameof operator, string interpolation, auto-properties and expression-bodied methods.


Code Cracker

Code Cracker is a smaller package for C# and VB with analyzers e.g. for empty catch blocks and if a disposable object is disposed.



SonarLint for C# has great analyzers too as Christiaan Rakowski points out in the comments. One of them warns about logical paths that will never be reached or simplified.

Roslyn_conditionalstructurePlatform Specific Analyzer

With Windows 10 and the new Universal Windows Platform you as the developer need to make sure that the Windows App does not use an API not supported on the platform you are targeting. This is exactly what the Platform Specific Analyzer package does for both C# and VB.


If you know any other great packages – let me know.

How-to start and stop Azure VMs at a schedule

I use Azure VMs for dev/test and I do not want them to run all night, as I have to pay for it. Therefore, I stop the VMs at night with a scheduler, as I do not always remember to stop the VMs after use.

Azure Automation is the right tool for the job. Azure Automation automates Azure management tasks and orchestrates actions across external systems from within Azure. You need an Azure Automation Account, which is a container for all your runbooks, runbook executions (jobs), and the assets that your runbooks depend on.

To execute runbooks, a set of user credentials needs to be stored as an asset. Create a new user as described in Azure Automation: Authenticating to Azure using Azure Active Directory.

Below, see guide on how to create the Azure Automation account and the runbook.


The new Azure Automation account lybAutomation and the runbook Stop Windows Azure Virtual Machines on a Schedule are created from the gallery. The content in the gallery comes from the Azure Script Center. The Azure Script Center has many PowerShell scripts covering many scenarios, but not all can be used with Azure Automation, as some scripts use features not available in Azure Automation. You do get a warning if you select one that is not supported, but in my mind, it should not be available in the gallery at all.

It burned me the first time I tried Azure Automation. I used the Stop Windows Azure Virtual Machines on a Schedule from the gallery, but it uses an on-premise scheduler.

You need to store the credentials in the runbook of the user created earlier. See below.
SetupRunBookCredentialsThen you need to configure the runbook script with the credentials and the Azure subscription where the virtual machines reside. See below.
ConfigureRunbookVmStopYou find your subscription name in the top bar “Subscriptions” of the Azure portal.
Now you can test your runbook and all you need is to set up the schedule, so it runs every evening. See guide below.


Be aware that the time is in UTC, so you have to correct the time according to your time zone. I expect the scheduler to get an overhaul, as it is too simple right now.

Minimizing the cost of dev/test environments in Azure

I use Windows Azure as my dev/test environment because it is fast and convenient to create new virtual machines or services. I use the MSDN Subscription Azure Benefits, which includes free Azure Credits. The Azure Credits cover my dev/test needs even though I use more than a handful of VMs and services. I make smart use of the free Azure Credits by turning off VMs at night and during weekends, when I am not using them. Which means that I can use 3-4 times more VMs on Azure compared to just letting them run all the time. VMs are costly compared to the PaaS services such as Azure WebSites, SQL Azure and Cloud Services. So the PaaS services are not a cost issue.

I manage the Azure VMs and almost everything with the Server Explorer in Visual Studio. It is a quick way to start VMs in the morning.


If I have a list of VMs that I need to manage, then I use the Azure PowerShell cmdlets – see my How-to start and stop Azure VMs via PowerShell.

Finally, I use Azure Automation to ensure that I never have a running Azure VM all night, just because I forgot to shut it down – see How-to start and stop Azure VMs at a schedule. It automatically shuts down any VM running in my MSDN Subscription at 6 p.m. If I work later, I can just start the required VMs again – it only take a couple of minutes.