Weekly Windows Dockerfile #6
There are 52 Dockerfiles in the source code for my book, Docker on Windows. Perfect for a year-long blog series.
Each week I'll look at one Dockerfile in detail, showing you what it does and how it works. This is #6 in the series.
It's the same .NET Core "Hello World" console app from #4 and #5 but now using Docker multi-stage builds.
Multi-stage builds use a Dockerfile with multiple
FROM instructions. Each
FROM starts a new stage in the build process. Intermediate stages are only used during the build, and it's the final stage that packages the application image.
Multi-stage builds make your app truly portable. You compile the app in one stage of the build, and package the compiled output in the final stage.
With Docker multi-stage builds, anyone can build and run your app from source with just one dependency: Docker.
The compile stage of the build uses a Docker image which has the toolchain installed. The app is compiled in a container, so developers use the exact same build tools as the CI process. The CI servers don't need any tools installed except Docker.
This example uses .NET Core, but you can use multi-stage builds for any runtime - like .NET Framework apps running in Windows containers and Node.js and Java apps running in Linux containers.
Dockerfile.multistage is very simple:
FROM microsoft/dotnet:1.1-sdk-nanoserver AS builder WORKDIR /src COPY src/ . RUN dotnet restore; dotnet publish # final image stage FROM microsoft/dotnet:1.1-runtime-nanoserver WORKDIR /dotnetapp COPY --from=builder /src/bin/Debug/netcoreapp1.1/publish . CMD ["dotnet", "HelloWorld.NetCore.dll"]
Stage 1 is the build process. It starts from the microsoft/dotnet image, using the variant built on Nano Server, with the .NET Core 1.1 SDK installed:
FROM... AS builder- the normal
FROMinstruction, specifying the base image to use, but the
ASparameter lets you label the stage so you can refer to it later in the Dockerfile
COPYjust sets up the target directory and copies in the source code from the host
RUNcompiles the app using
It's better to break out the restore and build steps, to make use of Docker's image cache and speed up your build process - but I'll cover that later in the series.
At the end of this stage, the published app binaries are available in the builder at a known location.
Stage 2 packages the app. It uses the slimmed-down
microsoft/dotnet image with the 1.1 runtime installed, but not the SDK. There's nothing new here except:
COPY --from=builder...copies the published app output into the final image. It's the normal
COPYinstruction syntax, but the
fromparameter specifies a previous stage in the build to use as the file source, rather than the host running the build.
Like the original example you don't need .NET Core installed to build and run this app. The toolchain is all in the SDK image used in the first stage of the build, so you can just clone the sixeyed/docker-on-windows repo, and run:
cd .\ch02\ch02-dotnet-helloworld docker image build ` --tag docker-on-windows/ch02-dotnet-hello-world:multistage ` --file .\Dockerfile.multistage .
You'll see all the output from NuGet and MSBuild running inside the build container:
Now the app is packaged in a Docker image, but like the slim example, the app image uses a slimmer base image, with just the bare minimum needed to run the published app.
The output is a Docker image like any other, the build stages are discarded and they're not part of the final image. You can push and pull the image to Docker registries and run containers from it in the usual way:
docker container run ` docker-on-windows/ch02-dotnet-hello-world:multistage
Multi-stage builds bring a whole lot of benefits to your project:
the entire build and deployment process is encapsulated in the Dockerfile, there's no proliferation of build scripts to navigate
the toolchain is fixed in the Dockerfile, so it's the same for everyone. You won't get into the situation where devs and CI agents have drifting toolchains and builds which work in dev fail on the server
your on-boarding requirements are minimal. New devs on the team and new CI servers only need two things: the source tree and Docker. You won't lose half a day installing tools just to run a new project
you don't need Visual Studio installed on the servers, so you can run headless build agents based on Windows Server Core. That means less patching and more automation
devs can even use different IDEs if they want to. They can code away in Visual Studio, Rider or VS Code. They'll all use the same build process when they run the app locally in a container.
You can do interesting things with multi-stage builds, it's not just about compiling the app. If you have specific dependencies for your build process or your app, you can use multiple stages with other Docker images as the source for dependencies.
Later in the book I package Jenkins to run in a Docker container on Windows. The Dockerfile starts like this:
FROM dockeronwindows/ch10-git AS git FROM dockeronwindows/ch10-docker AS docker FROM dockeronwindows/ch10-jenkins-base
Chapter 2 explores the Dockerfile syntax and the image building process. Next week it's dockeronwindows/ch02-static-website, a simple website which demonstrates how Docker uses temporary containers during the build.