Docker For Microsoft Windows



You can run any application in Docker as long as it can be installed and executed unattended, and the base operating system supports the app. Windows Server Core runs in Docker which means you can run pretty much any server or console application in Docker.

TL;DR

Update! For a full walkthrough on Dockerizing Windows apps, check out my book Docker on Windows and my Pluralsight course Modernizing .NET Apps with Docker.

Docker Desktop for Windows is available for free and provides a development environment for building, shipping, and running dockerized apps. By enabling the WSL 2 based engine, you can run both Linux and Windows containers in Docker Desktop on the same machine. Overview of Docker containers.

A Dockerfile must be created with no extension. To do this in Windows, create the file with your editor of choice, then save it with the notation 'Dockerfile' (including the quotes). Docker is an open-source project for automating the deployment of applications as portable, self-sufficient containers that can run on the cloud or on-premises. Docker is also a company that promotes and evolves this technology, working in collaboration with cloud, Linux, and Windows vendors, including Microsoft. Docker Desktop is an application for MacOS and Windows machines for the building and sharing of containerized applications and microservices. Docker Desktop delivers the speed, choice and security you need for designing and delivering containerized applications on your desktop. You need Windows Server to run 'pure' Docker containers, where the container process runs directly on the host OS. You can use the same Docker images, the same Dockerfiles and the same docker commands on Windows 10, but there's an additional virtualization overhead, so it's good to use a Windows Server VM for test environments.

Check out these examples:

  • openjdk:windowsservercore - Docker image with the Java runtime on Windows Server Core, by Docker CaptainStefan Scherer
  • elasticsearch:nanoserver - Docker image with a Java app on Nano Server
  • kibana:windowsservercore - Docker image with a Node.js app on Windows Server Core
  • nats:nanoserver - Docker image with a Go app on Nano Server
  • nerd-dinner - Docker image with an ASP.NET app on Windows Server Core
  • dotnetapp - Docker image with a .NET Core app on Nano Server

Lately I've been Dockerizing a variety of Windows apps - from legacy .NET 2.0 WebForms apps to Java, .NET Core, Go and Node.js. Packaging Windows apps as Docker images to run in containers is straightforward - here's the 5-step guide.

1. Choose Your Base Image

Docker images for Windows apps need to be based on microsoft/nanoserver or microsoft/windowsservercore, or on another image based on one of those.

Which you use will depend on the application platform, runtime, and installation requirements. For any of the following you need Windows Server Core:

  • .NET Framework apps
  • MSI installers for apps or dependencies
  • 32-bit runtime support

For anything else, you should be able to use Nano Server. I've successfully used Nano Server as the base image for Go, Java and Node.js apps.

Nano Server is preferred because it is so drastically slimmed down. It's easier to distribute, has a smaller attack surface, starts more quickly, and runs more leanly.

Being slimmed down may have problems though - certain Windows APIs just aren't present in Nano Server, so while your app may build into a Docker image it may not run correctly. You'll only find that out by testing, but if you do find problems you can just switch to using Server Core.

Unless you know you need Server Core, you should start with Nano Server. Begin by running an interactive container with docker run -it --rm microsoft/nanoserver powershell and set up your app manually. If it all works, put the commands you ran into a Dockerfile. If something fails, try again with Server Core.

Derived Images

You don't have to use a base Windows image for your app. There are a growing number of images on Docker Hub which package app frameworks on top of Windows.

They are a good option if they get you started with the dependencies you need. These all come in Server Core and Nano Server variants:

  • microsoft/iis - basic Windows with IIS installed
  • microsoft/aspnet - ASP.NET installed on top of IIS
  • microsoft/aspnet:3.5 - .NET 3.5 installed and ASP.NET set up
  • openjdk - OpenJDK Java runtime installed
  • golang - Go runtime and SDK installed
  • microsoft/dotnet - .NET runtime and SDK installed.

A note of caution about derived images. When you have a Windows app running in a Docker container, you don't connect to it and run Windows Update to apply security patches. Instead, you build a new image with the latest patches and replace your running container. To support that, Microsoft release regular updates to the base images on Docker Hub, tagging them with a full version number (10.0.14393.693 is the current version).

Base image updates usually happen monthly, so the latest Windows Server Core and Nano Server images have all the latest security patches applied. If you build your images from the Windows base image, you just need to rebuild to get the latest updates. If you use a derived image, you have a dependency on the image owner to update their image, before you can update yours.

If you use a derived image, make sure it has the same release cadence as the base images. Microsoft's images are usually updated at the same time as the Windows image, but official images may not be.

Alternatively, use the Dockerfile from a derived image to make your own 'golden' image. You'll have to manage the updates for that image, but you will control the timescales. (And you can send in a PR for the official image if you get there first).

2. Install Dependencies

You'll need to understand your application's requirements, so you can set up all the dependencies in the image. Both Nano Server and Windows Server Core have PowerShell set up, so you can install any software you need using PowerShell cmdlets.

Remember that the Dockerfile will be the ultimate source of truth for how to deploy and run your application. It's worth spending time on your Dockerfile so your Docker image is:

  • Repeatable. You should be able to rebuild the image at any time in the future and get exactly the same output. You should specify exact version numbers when you install software in the image.
  • Secure. Software installation is completely automated, so you should make sure you trust any packages you install. If you download files as part of your install, you can capture the checksum in the Dockerfile and make sure you verify the file after download.
  • Minimal. The Docker image you build for your app should be as small as possible, so it's fast to distribute and has a small surface area. Don't install anything more than you need, and clean up any installations as you go.

Adding Windows Features

Windows features can be installed with Add-WindowsFeature. If you want to see what features are available for an image, start an interactive container with docker run -it --rm microsoft/windowsservercore powershell and run Get-WindowsFeature.

On Server Core you'll see that .NET 4.6 is already installed, so you don't need to add features to run .NET Framework applications.

.NET is backwards-compatible, so you can use the installed .NET 4.6 to run any .NET application, back to .NET 2.0. In theory .NET 1.x apps can run too. I haven't tried that.

If you're running an ASP.NET web app but you want to use the base Windows image and control all your dependencies, you can add the Web Server and ASP.NET features:

Downloading Files

There's a standard pattern for installing dependencies from the Internet - here's a simple example for downloading Node.js into your Docker image:

The version of Node to download and the expected SHA-256 checksum are captured as environment variables with the ENV instruction. That makes it easy to upgrade Node in the future - just change the values in the Dockerfile and rebuild. It also makes it easy to see what version is present in a running container, you can just check the environment variable.

The download and hash check is done in a single RUN instruction, using Invoke-WebRequest to download the file and then Get-FileHash to verify the checksum. If the hashes don't match, the build fails.

Docker For Microsoft Windows Download

After these instructions run, your image has the Node.js runtime in a known location - C:nodenode.exe. It's a known version of Node, verified from a trusted download source.

Expanding Archives

For dependencies that come packaged, you'll need to install them as part of the RUN instruction. Here's an example for Elasticsearch which downloads and uncompresses a ZIP file:

It's the same pattern as before, capturing the checksum, downloading the file and checking the hash. In this case, if the hash is good the file is uncompressed with Expand-Archive, moved to a known location and the Zip file is deleted.

Don't be tempted to keep the Zip file in the image, 'in case you need it'. You won't need it - if there's a problem with the image you'll build a new one. And it's important to remove the package in the same RUN command, so the Zip file is downloaded, expanded and deleted in a single image layer.

It may take several iterations to build your image. While you're working on it, it's a good idea to store any downloads locally and add them to the image with COPY. That saves you downloading large files every time. When you have your app working, replace the COPY with the proper download-verify-deleteRUN pattern.

Installing MSIs

You can download and run MSIs using the same approach. Be aware that not all MSIs will be built to support unattended installation. A well-built MSI will support command-line switches for any options available in the UI, but that isn't always the case.

If you can install the app from an MSI you'll also need to ensure that the install completed before you move on to the next Dockerfile instruction - some MSIs continue to run in the background. This example from Stefan Scherer's iisnode Dockerfile uses Start-Process ... -Wait to run the MSI:

3. Deploy the Application

Docker for microsoft windows 7

Packaging your own app will be a simplified version of step 2. If you already have a build process which generates an unattended-friendly MSI, you can can copy it from the local machine into the image and install it with msiexec:

This example is from the Modernize ASP.NET Apps - Ops Lab from Docker Labs on GitHub. The MSI supports app configuration with the RELEASENAME option, and it runs unattended with the qn flag.

With MSIs and other packaged deployment options (like Web Deploy) you need to choose between using what you currently have, or changing your build output to something more Docker friendly.

Web Deploy needs an agent installed into the image which adds an unnecessary piece of software. MSIs don't need an agent, but they're opaque, so it's not clear what's happening when the app gets installed. The Dockerfile isn't an explicit deployment guide if some of the steps are hidden.

An xcopy deployment approach is better, where you package the application and its dependencies into a folder and copy that folder into the image. Your image will only run a single app, so there won't be any dependency clashes.

This example copies an ASP.NET Web app folder into the image, and configures it with IIS using PowerShell:

If you're looking at changing an existing build process to produce your app package, you should think about building your app in Docker too. Consolidating the build in a multi-stage Dockerfile means you can build your app anywhere without needing to install .NET or Visual Studio.

See Dockerizing .NET Apps with Microsoft's Build Images on Docker Hub.

4. Configure the Entrypoint

When you run a container from an image, Docker starts the process specified in the CMD or ENTRYPOINT instruction in the Dockerfile.

Modern app frameworks like .NET Core, Node and Go run as console apps - even for Web applications. That's easy to set up in the Dockerfile. This is how to run the open source Docker Registry - which is a Go application - inside a container:

Here registry is the name of the executable, and the other values are passed as options to the exe.

ENTRYPOINT and CMD work differently and can be used in conjunction. See how CMD and ENTRYPOINT interact to learn how to use them effectively.

Starting a single process is the ideal way to run apps in Docker. The engine monitors the process running in the container, so if it stops Docker can raise an error. If it's also a console app, then log entries written by the app are collected by Docker and can be viewed with docker logs.

For .NET web apps running in IIS, you need to take a different approach. The actual process serving your app is w3wp.exe, but that's managed by the IIS Windows service, which is running in the background.

IIS will keep your web app running, but Docker needs a process to start and monitor. In Microsoft's IIS image they use a tool called ServiceMonitor.exe as the entrypoint. That tool continually checks a Windows service is running, so if IIS does fail the monitor process raises the failure to Docker.

Alternatively, you could run a PowerShell startup script to monitor IIS and add extra functionality - like tailing the IIS log files so they get exposed to Docker.

5. Add a Healthcheck

HEALTHCHECK is one of the most useful instructions in the Dockerfile and you should include one in every app you Dockerize for production. Healthchecks are how you tell Docker if the app inside your container is healthy.

Docker monitors the process running in the container, but that's just a basic liveness check. The process could be running, but your app could be in a failed state - for a .NET Core app, the dotnet executable may be up but returning 503 to every request. Without a healthcheck, Docker has no way to know the app is failing.

A healthcheck is a script you define in the Dockerfile, which the Docker engine executes inside the container at regular intervals (30 seconds by default, but configurable at the image and container level).

This is a simple healthcheck for a web application, which makes a web request to the local host (remember the healthcheck executes inside the container) and checks for a 200 response status:

Healthcheck commands need to return 0 if the app is healthy, and 1 if not. The check you make inside the healthcheck can be as complex as you like - having a diagnostics endpoint in your app and testing that is a thorough approach.

Make sure your HEALTHCHECK command is stable, and always returns 0 or 1. If the command itself fails, your container may not start.

Any type of app can have a healthcheck. Michael Friis added this simple but very useful check to the Microsoft SQL Server Express image:

The command verifies that the SQL Server database engine is running, and is able to respond to a simple query.

There are additional advantages in having a comprehensive healthcheck. The command runs when the container starts, so if your check exercises the main path in your app, it acts as a warm-up. When the first user request hits, the app is already running warm so there's no delay in sending the response.

Healthchecks are also very useful if you have expiry-based caching in your app. You can rely on the regular running of the healthcheck to keep your cache up-to date, so you could cache items for 25 seconds, knowing the healthcheck will run every 30 seconds and refresh them.

Dockerizing Windows apps is straightforward. The Dockerfile syntax is clean and simple, and you only need to learn a handful of instructions to build production-grade Docker images based on Windows Server Core or Nano Server.

Following these steps will get you a functioning Windows app in a Docker image - then you can look to optimizing your Dockerfile.

-->

The Docker engine includes tools that automate container image creation. While you can create container images manually by running the docker commit command, adopting an automated image creation process has many benefits, including:

  • Storing container images as code.
  • Rapid and precise recreation of container images for maintenance and upgrade purposes.
  • Continuous integration between container images and the development cycle.

The Docker components that drive this automation are the Dockerfile, and the docker build command.

The Dockerfile is a text file that contains the instructions needed to create a new container image. These instructions include identification of an existing image to be used as a base, commands to be run during the image creation process, and a command that will run when new instances of the container image are deployed.

Docker build is the Docker engine command that consumes a Dockerfile and triggers the image creation process.

This topic will show you how to use Dockerfiles with Windows containers, understand their basic syntax, and what the most common Dockerfile instructions are.

This document will discuss the concept of container images and container image layers. If you want to learn more about images and image layering, see container base images.

For a complete look at Dockerfiles, see the Dockerfile reference.

Basic Syntax

In its most basic form, a Dockerfile can be very simple. The following example creates a new image, which includes IIS, and a ‘hello world’ site. This example includes comments (indicated with a #), that explain each step. Subsequent sections of this article will go into more detail on Dockerfile syntax rules, and Dockerfile instructions.

Note

A Dockerfile must be created with no extension. To do this in Windows, create the file with your editor of choice, then save it with the notation 'Dockerfile' (including the quotes).

For additional examples of Dockerfiles for Windows, see the Dockerfile for Windows repository.

Instructions

Dockerfile instructions provide the Docker Engine the instructions it needs to create a container image. These instructions are performed one-by-one and in order. The following examples are the most commonly used instructions in Dockerfiles. For a complete list of Dockerfile instructions, see the Dockerfile reference.

FROM

The FROM instruction sets the container image that will be used during the new image creation process. For instance, when using the instruction FROM mcr.microsoft.com/windows/servercore, the resulting image is derived from, and has a dependency on, the Windows Server Core base OS image. If the specified image is not present on the system where the Docker build process is being run, the Docker engine will attempt to download the image from a public or private image registry.

The FROM instruction's format goes like this:

Here's an example of the FROM command:

To download the ltsc2019 version windows server core from the Microsoft Container Registry (MCR):

For more detailed information, see the FROM reference.

RUN

The RUN instruction specifies commands to be run, and captured into the new container image. These commands can include items such as installing software, creating files and directories, and creating environment configuration.

The RUN instruction goes like this:

The difference between the exec and shell form is in how the RUN instruction is executed. When using the exec form, the specified program is run explicitly.

Here's an example of the exec form:

The resulting image runs the powershell New-Item c:/test command:

To contrast, the following example runs the same operation in shell form:

The resulting image has a run instruction of cmd /S /C powershell New-Item c:test.

Considerations for using RUN with Windows

On Windows, when using the RUN instruction with the exec format, backslashes must be escaped.

When the target program is a Windows installer, you'll need to extract the setup through the /x:<directory> flag before you can launch the actual (silent) installation procedure. You must also wait for the command to exit before you do anything else. Otherwise, the process will end prematurely without installing anything. For details, please consult the example below.

Examples of using RUN with Windows

The following example Dockerfile uses DISM to install IIS in the container image:

This example installs the Visual Studio redistributable package. Start-Process and the -Wait parameter are used to run the installer. This ensures that the installation completes before moving on to the next instruction in the Dockerfile.

For detailed information on the RUN instruction, see the RUN reference.

COPY

The COPY instruction copies files and directories to the container's file system. The files and directories must be in a path relative to the Dockerfile.

The COPY instruction's format goes like this:

If either source or destination includes white space, enclose the path in square brackets and double quotes, as shown in the following example:

Considerations for using COPY with Windows

On Windows, the destination format must use forward slashes. For example, these are valid COPY instructions:

Meanwhile, the following format with backslashes won't work:

Examples of using COPY with Windows

The following example adds the contents of the source directory to a directory named sqllite in the container image:

The following example will add all files that begin with config to the c:temp directory of the container image:

For more detailed information about the COPY instruction, see the COPY reference.

ADD

The ADD instruction is like the COPY instruction, but with even more capabilities. In addition to copying files from the host into the container image, the ADD instruction can also copy files from a remote location with a URL specification.

The ADD instruction's format goes like this:

If either the source or destination include white space, enclose the path in square brackets and double quotes:

Considerations for running ADD with Windows

On Windows, the destination format must use forward slashes. For example, these are valid ADD instructions:

Meanwhile, the following format with backslashes won't work:

Additionally, on Linux the ADD instruction will expand compressed packages on copy. This functionality is not available in Windows.

Examples of using ADD with Windows

The following example adds the contents of the source directory to a directory named sqllite in the container image:

The following example will add all files that begin with 'config' to the c:temp directory of the container image.

The following example will download Python for Windows into the c:temp directory of the container image.

For more detailed information about the ADD instruction, see the ADD reference.

WORKDIR

The WORKDIR instruction sets a working directory for other Dockerfile instructions, such as RUN, CMD, and also the working directory for running instances of the container image.

The WORKDIR instruction's format goes like this:

Considerations for using WORKDIR with Windows

On Windows, if the working directory includes a backslash, it must be escaped.

Examples

For detailed information on the WORKDIR instruction, see the WORKDIR reference.

CMD

The CMD instruction sets the default command to be run when deploying an instance of the container image. For instance, if the container will be hosting an NGINX web server, the CMD might include instructions to start the web server with a command like nginx.exe. If multiple CMD instructions are specified in a Dockerfile, only the last is evaluated.

The CMD instruction's format goes like this:

Considerations for using CMD with Windows

On Windows, file paths specified in the CMD instruction must use forward slashes or have escaped backslashes . The following are valid CMD instructions:

However, the following format without the proper slashes will not work:

For more detailed information about the CMD instruction, see the CMD reference.

Escape character

In many cases a Dockerfile instruction will need to span multiple lines. To do this, you can use an escape character. The default Dockerfile escape character is a backslash . However, because the backslash is also a file path separator in Windows, using it to span multiple lines can cause problems. To get around this, you can use a parser directive to change the default escape character. For more information about parser directives, see Parser directives.

The following example shows a single RUN instruction that spans multiple lines using the default escape character:

To modify the escape character, place an escape parser directive on the very first line of the Dockerfile. This can be seen in the following example.

Note

Only two values can be used as escape characters: and `.

For more information about the escape parser directive, see Escape parser directive.

Docker Microsoft Windows Server 2016

PowerShell in Dockerfile

PowerShell cmdlets

PowerShell cmdlets can be run in a Dockerfile with the RUN operation.

REST calls

PowerShell's Invoke-WebRequest cmdlet can be useful when gathering information or files from a web service. For instance, if you build an image that includes Python, you can set $ProgressPreference to SilentlyContinue to achieve faster downloads, as shown in the following example.

Docker For Microsoft Windows 10

Another option for using PowerShell to download files during the image creation process is to use the .NET WebClient library. This can increase download performance. The following example downloads the Python software, using the WebClient library.

Note

Nano Server does not currently support WebClient.

PowerShell scripts

In some cases, it may be helpful to copy a script into the containers you use during the image creation process, then run the script from within the container.

Note

This will limit any image layer caching and decrease the Dockerfile's readability.

This example copies a script from the build machine into the container using the ADD instruction. This script is then run using the RUN instruction.

Docker build

Once a Dockerfile has been created and saved to disk, you can run docker build to create the new image. The docker build command takes several optional parameters and a path to the Dockerfile. For complete documentation on Docker Build, including a list of all build options, see the build reference.

The format of the docker build command goes like this:

For example, the following command will create an image named 'iis.'

When the build process has been initiated, the output will indicate status and return any thrown errors.

The result is a new container image, which in this example is named 'iis.'

Further reading and references