This is part two in a three-part series on working with Windows Server Containers. The guidance here is drawn from a real-world implementation Zero Friction performed, migrating from an unreliable Cloud Services Worker Role, to 17 independently-deployable, scalable and monitored containers on AKS.
There are multiple variants of the Windows Server OS: Nano, Core and Standard. The differences between them are important when containerising.
Windows Server Standard
This is the variant you know and love (ahem). It's Windows "full", with all of the features anyone could ever possibly want. It's what you've been working with for years, and is likely to be what you continue to use as a host OS in a number of scenarios, or in Servers that need to run GUI applications. Though an image does exist, you really shouldn't run this container image; keep it for VMs and "bare-metal" hosts only.
Windows Server Core
Core is a compromise between Standard and Nano. It is CLI-only, and has a smaller more focused set of services, but will remain quite familiar to people who've only worked with Windows Server Standard in the past. The full .NET Framework is supported, along with a much wider range of Windows Components like AD and friends. Server Core is designed to be a stepping stone to modernising .NET Framework applications: it allows you to get most of the benefits of Containerisation without having to make dramatic changes to your applications, however this comes at a cost of a much larger base image size (2.12gb), and much slower container start-up times.
Windows Nano Server
Nano is a container-only variant - you can't get an ISO for this one to install on a VM or bare metal. Coming in at 256mb for the base image, it is dramatically lighter both to send over the wire (102.7mb compressed) and to spin up an instance of. Nano Server is command-line only (no UI), and is very focused in what it supports. .NET Framework is not supported, only .NET core. There is no option to run large Windows Server components like Active Directory, SCCM or even 32-bit apps: Nano is for scenarios where you'll have many containers coming together to make up a distributed application, and they'll be running a tiny, focused set of services.
Windows Server Versions
Something that can be immensely confusing if you've not been paying close attention to Windows Server versioning over the past few years (i.e. if you're coming at this problem from a developer- rather than sysadmin-perspective) is the naming and versioning approach for releases of Windows Server (release version vs OS variant). Further complicating this issue is that the various Microsoft-official images for things like the .NET Framework and .NET Core are inconsistent in the way they tag their image releases. They're grouped into two channels: Long Term Servicing Channel (LTSC), and Semi-Annual Channel (SAC). LTSC is conservative and stable, SAC gets features earlier. SAC builds off the previous LTSC. So here it is, decoded:
|Windows Server 2016||LTSC||2016-09-26||10.0.14393||
|Windows Server 1709||SAC||2017-09-17||
|Windows Server 1803||SAC||2018-03-xx||
|Windows Server 2019||LTSC||2018-10-02||10.0.17623||
|Windows Server 1903||SAC||2019-05-21||
In particular, Windows Server 2019 being referred to as both
1809 depending on where it is used tripped me up. Hopefully it'll make more sense now!
Considerations when choosing Windows Server versions
There are two main constraints to consider when choosing host and container OS versions:
- When building Windows Server Containers, the Host OS must match the Base OS major release of the image you're building.
- Windows Server 2019 images can only be built on a Windows Server 2019 host
- When running Windows Server Containers, the Host OS must be equal to or newer than the Base OS major release of the container you're running.
- Windows Server 2019 images can be run on hosts running Windows Server 2019 or Windows Server 1903
- Windows Server 2016 images can be run on hosts running any of the above Windows Server versions
Within the constraints above, if you're planning to run and manage your own Docker Hosts (eg running Docker on its own) then you have free choice of which OSes to target. However, the newer OS versions have vastly improved support for containerisation, as both the hosting and base OS images have received a lot of optimisation. For example, while the
windows/servercore:1903 image is 4.83gb, the
windows/servercore:ltsc2016 was a whopping 11.1gb. Additionally, the newer images drastically decrease the memory footprint of running containers by optimising the container runtime to be much more efficient in how much of the kernel can be shared, as opposed to the original Windows Server Containers which were basically full VM images.
If you're running on a managed service such as Azure Kubernetes Services (AKS), you get much less choice and you'll have to work within the constraints of your hosting platform: as at Kubernetes 1.14 (which is the version AKS uses for the Windows Server Containers preview), your nodes are provisioned as Windows Server 2019 hosts, and you must use containers based off the Windows Server 2019 images.
A note on the
When starting out on Docker, it seems intuitive that you'll want to get the most up-to-date container images possible, and many of the examples online use the
There is nothing special about this tag, it's just a convention that most image repositories follow. However, unlike majority of other tags,
latest has a very high change cadence: it usually changes every time a new image is pushed to a repository. Relying on the
latest tag can lead to pretty huge and unexpected consequences for your Docker images; referring to
latest, you basically say "I'll take whatever you choose to release, thanks".
When dealing with Windows Server Container images, and with Microsoft's much more regular release cadence, you can be caught unawares by this. For example, if I built a Docker image in September 2018 based off
dotnet/framework/runtime:latest, I would have been receiving an image derived from the
1803 release of the Windows Server 2016 family. Perhaps my CI/CD agent had this image cached so that our builds are nice and snappy. But then, suddenly on October 2nd 2018, an entirely new release (Windows Server 2019) becomes generally available. Microsoft, as they should, updates the dotnet framework images with new images based off WS2019, and the
dotnet/framework/runtime:latest tag moves to refer to the 2019 version instead.
Now my image is suddenly based off an entirely new family of the OS. This could contain breaking architectural or framework changes, undiscovered security vulnerabilities, compatibility issues with my dependencies, and so on. To make matters worse, the WS2019 image is not cached on my build agent, so suddenly my builds go from 30 seconds to 10 minutes as a whole new image needs to be pulled down each build.
So, for the most consistent results and control over when you accept (most) changes, don't use the
latest tag when choosing your base images. The other tags can still move, but the change impact will be much smaller.
If you need to be 100% deterministic about your base images, consider referencing them by digest instead of by tag.
Summary - choosing a base Windows Server image
- If you're running dotnet core, you're free to run on a Linux image. Go this path unless you have other dependencies that require Windows. The Linux container ecosystem is way more mature than the Windows ecosystem.
- If you must run on Windows, you want to aim toward getting on Windows Nano Server. This one is specifically designed/optimised for container scenarios, and has much smaller base image sizes.
- If you need .NET Framework (vs dotnet core) support, you'll need to be on Windows Server Core
- Whichever variant you're on, use the newest tagged version that your circumstance allows (eg
- Don't use the
latesttag for your base image, as you will get unexpected version bumps. Choose a specific version-related tag instead.
We'll soon be publishing the third and final part in this series, Windows Server Containers, Part 3 - Continuous Integration and Delivery.