This is an update to my previous post explaining the difference between the various Linux .NET docker files. Things have changed a lot in .NET Core 2.1, so that post is out of date!
When you build and deploy an application in Docker, you define how your image should be built using a Dockerfile. This file lists the steps required to create the image, for example: set an environment variable, copy a file, or run a script. Whenever a step is run, a new layer is created. Your final Docker image consists of all the changes introduced by these layers in your Dockerfile.
Typically, you don't start from an empty image where you need to install an operating system, but from a "base" image that contains an already configured OS. For .NET development, Microsoft provide a number of different images depending on what it is you're trying to achieve.
In this post, I look at the various Docker base images available for .NET Core development, how they differ, and when you should use each of them. I'm only going to look at the Linux amd64 images, but there are Windows container versions and even Linux arm32 images available too. At the time of writing (just after the .NET Core 2.1 release) the latest images available are
2.1.300 for the various runtime and SDK images respectively.
Note: You should normally be specific about exactly which version of a Docker image you build on in your Dockerfiles (e.g. don't use
latest). For that reason, all the images I mention in this post use the current latest version numbers,
I'll start by briefly discussing the difference between the .NET Core SDK and the .NET Core Runtime, as it's an important factor when deciding which base image you need. I'll then walk through each of the images in turn, using the Dockerfiles for each to explain what they contain, and hence what you should use them for.
tl;dr; This is a pretty long post, so for convenience, here's some links to the relevant sections and a one-liner use case:
microsoft/dotnet:2.1.0-runtime-deps- use for deploying self-contained deployment apps
microsoft/dotnet:2.1.0-runtime- use for deploying .NET Core console apps
microsoft/dotnet:2.1.0-aspnetcore-runtime- use for deploying ASP.NET Core apps
microsoft/dotnet:2.1.300-sdk- use for building .NET Core (or ASP.NET Core apps)
Note that all of these images use the
microsoft/dotnet repository - the previous
microsoft/aspnetcore-build repositories have both been deprecated. There is no true 2.1 equivalent to the old
microsoft/aspnetcore-build:2.0.3 image which included Node, Bower, and Gulp, or the
microsoft/aspnetcore-build:1.0-2.0 image which included multiple .NET Core SDKs. Instead, it's recommended you use MultiStage builds to achieve this instead.
One of the most often lamented aspects of .NET Core and .NET Core development, is around version numbers. There are so many different moving parts, and none of the version numbers match up, so it can be difficult to figure out what you need.
For example, on my dev machine I am building .NET Core 2.1 apps, so I installed the .NET Core 2.1 SDK to allow me to do so. When I look at what I have installed using
dotnet --info, I get (a more verbose version) of the following:
> dotnet --info .NET Core SDK (reflecting any global.json): Version: 2.1.300 Commit: adab45bf0c Runtime Environment: OS Name: Windows OS Version: 10.0.17134 Host (useful for support): Version: 2.1.0 Commit: caa7b7e2ba .NET Core SDKs installed: 1.1.9 [C:\Program Files\dotnet\sdk] ... 2.1.300 [C:\Program Files\dotnet\sdk] .NET Core runtimes installed: Microsoft.AspNetCore.All 2.1.0-preview1-final [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.NETCore.App 2.1.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] To install additional .NET Core runtimes or SDKs: https://aka.ms/dotnet-download
There's a lot of numbers there, but the important ones are
2.1.300 which is the version of the command line tools or SDK I'm currently using, and
2.1.0 which is the version of the .NET Core runtime.
In .NET Core 2.1,
dotnet --infolists all the runtimes and SDKs you have installed. I haven't shown all 20 I apparently have installed… I really need to claim some space back!
Whether you need the .NET Core SDK or the .NET Core runtime depends on what you're trying to do:
- The .NET Core SDK - This is what you need to build .NET Core applications.
- The .NET Core Runtime - This is what you need to run .NET Core applications.
When you install the SDK, you get the runtime as well, so on your dev machines you can just install the SDK. However, when it comes to deployment you need to give it a little more thought. The SDK contains everything you need to build a .NET Core app, so it's much larger than the runtime alone (122MB vs 22MB for the MSI files). If you're just going to be running the app on a machine (or in a Docker container) then you don't need the full SDK, the runtime will suffice, and will keep the image as small as possible.
For the rest of this post, I'll walk through the main Docker images available for .NET Core and ASP.NET Core. I assume you have a working knowledge of Docker - if you're new to Docker I suggest checking out Steve Gordon's excellent series on Docker for .NET developers.
- Contains native dependencies
- No .NET Core runtime or .NET Core SDK installed
- Use for running Self-Contained Deployment apps
The first image we'll look at forms the basis for most of the other .NET Core images. It actually doesn't even have .NET Core installed. Instead, it consists of the base
debian:stretch-slim image and has all the low-level native dependencies on which .NET Core depends.
The Docker images are currently all available in three flavours, depending on the OS image they're based on:
alpine:3.7. There are also ARM32 versions of the
ubuntuimages. In this post I'm just going to look at the
debianimages, as they are the default.
The Dockerfile consists of a single
RUN command that
apt-get installs the required dependencies on top of the base image, and sets a few environment variables for convenience.
FROM debian:stretch-slim RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ \ # .NET Core dependencies libc6 \ libgcc1 \ libgssapi-krb5-2 \ libicu57 \ liblttng-ust0 \ libssl1.0.2 \ libstdc++6 \ zlib1g \ && rm -rf /var/lib/apt/lists/* # Configure Kestrel web server to bind to port 80 when present ENV ASPNETCORE_URLS=http://+:80 \ # Enable detection of running in a container DOTNET_RUNNING_IN_CONTAINER=true
microsoft/dotnet:2.1.0-runtime-deps image is the basis for subsequent .NET Core runtime installations. Its main use is for when you are building self-contained deployments (SCDs). SCDs are apps that are packaged with the .NET Core runtime for the specific host, so you don't need to install the .NET Core runtime. You do still need the native dependencies though, so this is the image you need.
Note that you can't build SCDs with this image. For that, you'll need the SDK-based image described later in the post,
- Contains .NET Core runtime
- Use for running .NET Core console apps
The next image is one you'll use a lot if you're running .NET Core console apps in production.
microsoft/dotnet:2.1.0-runtime builds on the
runtime-deps image, and installs the .NET Core Runtime. It downloads the tar ball using
curl, verifies the hash, unpacks it, sets up symlinks and removes the old installer.
You can view the Dockerfile for the image here:
FROM microsoft/dotnet:2.1-runtime-deps-stretch-slim RUN apt-get update \ && apt-get install -y --no-install-recommends \ curl \ && rm -rf /var/lib/apt/lists/* # Install .NET Core ENV DOTNET_VERSION 2.1.0 RUN curl -SL --output dotnet.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-x64.tar.gz \ && dotnet_sha512='f93edfc068290347df57fd7b0221d0d9f9c1717257ed3b3a7b4cc6cc3d779d904194854e13eb924c30eaf7a8cc0bd38263c09178bc4d3e16281f552a45511234' \ && echo "$dotnet_sha512 dotnet.tar.gz" | sha512sum -c - \ && mkdir -p /usr/share/dotnet \ && tar -zxf dotnet.tar.gz -C /usr/share/dotnet \ && rm dotnet.tar.gz \ && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet
microsoft/dotnet:2.1.0-runtime image contains the .NET Core runtime, so you can use it to run any .NET Core 2.1 app such as a console app. You can't use this image to build your app, only to run it.
If you're running a self-contained app then you would be better served by the
runtime-deps image. Similarly, if you're running an ASP.NET Core app, then you should use the
microsoft/dotnet:2.1.0-aspnetcore-runtime image instead (up next), as it contains the shared runtime required for most ASP.NET Core apps.
- Contains .NET Core runtime and the ASP.NET Core shared framework
- Use for running ASP.NET Core apps
- Sets the default URL for apps to
.NET Core 2.1 moves away from the runtime store feature introduced in .NET Core 2.0, and replaces it with a series of shared frameworks. This is a similar concept, but with some subtle benefits (to cloud providers in particular, e.g. Microsoft). I wrote a post about the shared framework and the associated Microsoft.AspNetCore.App metapackage here.
By installing the Microsoft.AspNetCore.App shared framework, all the packages that make up the metapackage are already available, so when your app is published, it can exclude those dlls from the output. This makes your published output smaller, and improves layer caching for Docker images.
microsoft/dotnet:2.1.0-aspentcore-runtime image is very similar to the
microsoft/dotnet:2.1.0-runtime image, but instead of just installing the .NET Core runtime and shared framework, it installs the .NET Core runtime and the ASP.NET Core shared framework, so you can run ASP.NET Core apps, as well as .NET Core console apps.
You can view the Dockerfile for the image here:
FROM microsoft/dotnet:2.1-runtime-deps-stretch-slim RUN apt-get update \ && apt-get install -y --no-install-recommends \ curl \ && rm -rf /var/lib/apt/lists/* # Install ASP.NET Core ENV ASPNETCORE_VERSION 2.1.0 RUN curl -SL --output aspnetcore.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/$ASPNETCORE_VERSION/aspnetcore-runtime-$ASPNETCORE_VERSION-linux-x64.tar.gz \ && aspnetcore_sha512='0f37dc0fabf467c36866ceddd37c938f215c57b10c638d9ee572316a33ae66f7479a1717ab8a5dbba5a8d2661f09c09fcdefe1a3f8ea41aef5db489a921ca6f0' \ && echo "$aspnetcore_sha512 aspnetcore.tar.gz" | sha512sum -c - \ && mkdir -p /usr/share/dotnet \ && tar -zxf aspnetcore.tar.gz -C /usr/share/dotnet \ && rm aspnetcore.tar.gz \ && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet
Fairly obviously, for running ASP.NET Core apps! This is the image to use if you've published an ASP.NET Core app and you need to run it in production. It has the smallest possible footprint but all the necessary framework components and optimisations. You can't use it for building your app though, as it doesn't have the SDK installed. For that, you need the following image.
If you want to go really small, check out the new Alpine-based images - 163MB vs 255MB for the base image!
- Contains .NET Core SDK
- Use for building .NET Core and ASP.NET Core apps
All of the images shown so far can be used for running apps, but in order to build your app, you need the .NET Core SDK image. Unlike all the runtime images which use
debian:stretch-slim as the base, the
microsoft/dotnet:2.1.300-sdk image uses the
buildpack-deps:stretch-scm image. According to the Docker Hub description, the
…includes a large number of "development header" packages needed by various things like Ruby Gems, PyPI modules, etc.…a majority of arbitrary gem install / npm install / pip install should be successful without additional header/development packages…
stretch-scm tag also ensures common tools like
ca-certificates are installed.
microsoft/dotnet:2.1.300-sdk image installs the native prerequisites (as you saw in the
microsoft/dotnet:2.1.0-runtime-deps image), and then installs the .NET Core SDK. Finally, it sets some environment variables and warms up the NuGet package cache by running
dotnet help in an empty folder, which makes subsequent
dotnet operations faster.
You can view the Dockerfile for the image here:
FROM buildpack-deps:stretch-scm # Install .NET CLI dependencies RUN apt-get update \ && apt-get install -y --no-install-recommends \ libc6 \ libgcc1 \ libgssapi-krb5-2 \ libicu57 \ liblttng-ust0 \ libssl1.0.2 \ libstdc++6 \ zlib1g \ && rm -rf /var/lib/apt/lists/* # Install .NET Core SDK ENV DOTNET_SDK_VERSION 2.1.300 RUN curl -SL --output dotnet.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$DOTNET_SDK_VERSION/dotnet-sdk-$DOTNET_SDK_VERSION-linux-x64.tar.gz \ && dotnet_sha512='80a6bfb1db5862804e90f819c1adeebe3d624eae0d6147e5d6694333f0458afd7d34ce73623964752971495a310ff7fcc266030ce5aef82d5de7293d94d13770' \ && echo "$dotnet_sha512 dotnet.tar.gz" | sha512sum -c - \ && mkdir -p /usr/share/dotnet \ && tar -zxf dotnet.tar.gz -C /usr/share/dotnet \ && rm dotnet.tar.gz \ && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet # Configure Kestrel web server to bind to port 80 when present ENV ASPNETCORE_URLS=http://+:80 \ # Enable detection of running in a container DOTNET_RUNNING_IN_CONTAINER=true \ # Enable correct mode for dotnet watch (only mode supported in a container) DOTNET_USE_POLLING_FILE_WATCHER=true \ # Skip extraction of XML docs - generally not useful within an image/container - helps perfomance NUGET_XMLDOC_MODE=skip # Trigger first run experience by running arbitrary cmd to populate local package cache RUN dotnet help
This image has the .NET Core SDK installed, so you can use it for building your .NET Core and ASP.NET Core apps. Technically you can also use this image for running your apps in production as the SDK includes the runtime, but you shouldn't do that in practice. As discussed at the beginning of this post, optimising your Docker images in production is important for performance reasons, but the
microsoft/dotnet:2.1.300-sdk image weighs in at a hefty 1.73GB, compared to the 255MB for the
To get the best of both worlds, you should use this image (or one of the later images) to build your app, and one of the runtime images to run your app in production. You can see how to do this using Docker multi-stage builds in Scott Hanselman's post here, or in my blog series here.
In this post I walked through some of the common Docker images used in .NET Core 2.1 development. Each of the images have a set of specific use-cases, and it's important you use the right one for your requirements. These images have changed since I wrote the previous version of this post; if you're using an earlier version of .NET Core check out that one instead.