Docker on Windows
上QQ阅读APP看书,第一时间看更新

Compiling the application during the build

There are two common approaches to packaging your own apps in Docker images. The first is to use a base image which contains the application platform and the build tools. So in your Dockerfile, you copy the source code into the image and compile the app as a step during the image-building process.

This is a popular approach for public images, because it means that anyone can build the image without having the application platform installed locally. It also means that the tooling for the application is bundled with the image, so it can make it possible to debug and troubleshoot the application running in the container.

Here's an example with a simple .NET Core application. This Dockerfile is for the dockeronwindows/ch02-dotnet-helloworld:2e image:

FROM microsoft/dotnet:2.2-sdk-nanoserver-1809

WORKDIR /src
COPY src/ .

USER ContainerAdministrator
RUN dotnet restore && dotnet build
CMD ["dotnet", "run"]

The Dockerfile uses Microsoft's .NET Core image from Docker Hub as the base image. It's a specific variation of the image, one that is based on Nano Server release 1809 and has the .NET Core 2.2 SDK installed. The build copies in the application source code from the context, and compiles the application as part of the container build process.

There are three new instructions in this Dockerfile that you haven't seen before:

  1. WORKDIR specifies the current working directory. Docker creates the directory in the intermediate container if it doesn't already exist, and sets it to be the current directory. It remains the working directory for the subsequent instructions in the Dockerfile, and for containers when they run from the image.
  2. USER changes the current user in the build. Nano Server uses a least-privilege user by default. This switches to a built-in account in the container image, which has administrative rights.
  3. RUN executes a command inside an intermediate container and saves the state of the container after the command completes, creating a new image layer.

When I build this image, you'll see the dotnet command output, which is the application being compiled from the RUN instruction in the image build:

> docker image build --tag dockeronwindows/ch02-dotnet-helloworld:2e . 
Sending build context to Docker daemon 192.5kB
Step 1/6 : FROM microsoft/dotnet:2.2-sdk-nanoserver-1809
---> 90724d8d2438
Step 2/6 : WORKDIR /src
---> Running in f911e313b262
Removing intermediate container f911e313b262
---> 2e2f7deb64ac
Step 3/6 : COPY src/ .
---> 391c7d8f4bcc
Step 4/6 : USER ContainerAdministrator
---> Running in f08f860dd299
Removing intermediate container f08f860dd299
---> 6840a2a2f23b
Step 5/6 : RUN dotnet restore && dotnet build
---> Running in d7d61372a57b

Welcome to .NET Core!

...

You'll see this approach a lot on Docker Hub for applications built with languages like .NET Core, Go, and Node.js, where the tooling is easy to add to a base image. It means that you can set up an automated build on Docker Hub so Docker's servers build your image from a Dockerfile when you push code changes to GitHub. Servers can do this without having .NET Core, Go, or Node.js installed, because all the build dependencies are inside the base image.

This option means that the final image will be a lot bigger than it needs to be for a production application. Language SDKs and tooling will probably use more disk space than the app itself, but your end result should be the application; all the build tools taking up space in your image will never be used when the container runs in production. An alternative is to build the application first, and then package the compiled binaries into your container image.