天天看點

Docker Builders:Builder pattern vs. Multi-stage builds in Docker

原文連結

Builder pattern vs. Multi-stage builds in Docker

This post looks at two new PRs from the Docker project that vastly improve the developer experience for building small images efficiently.

These changes are bleeding edge and are not available in a release yet, but I wanted to test them out.

A Docker PR has just been merged to enable multi-stage builds and a second PR opened just after that to improve the UX even further.

Happy day: https://t.co/WyXdLexRBq — Darren Shepherd (@ibuildthecloud) March 24, 2017

This is the first PR that adds multi-staged builds and has been merged.

Bleeding edge: multi-staged builds in @docker mean producing tiny images without hassle. https://t.co/bGporddWyW — Alex Ellis (@alexellisuk) March 24, 2017

This second PR improves the UX but was not yet merged at the time of writing.

With a statically compiled language like Golang people tended to derive their Dockerfiles from the Golang "SDK" image, add source, do a build then push it to the Docker Hub.

Unfortunately the size of the resulting image was quite large - at least 670mb.

A workaround which is informally called the builder pattern involves using two Docker images - one to perform a build and another to ship the results of the first build without the penalty of the build-chain and tooling in the first image.

Golang isn't the only language that can benefit from using one base image to build assets and a second image to run them. My work with Windows Containers also used this pattern to produce smaller images.

An example of the builder pattern:

Derive from a Golang base image with the whole runtime/SDK (Dockerfile.build)

Add source code

Produce a statically-linked binary

Copy the static binary from the image to the host (<code>docker create</code>, <code>docker cp</code>)

Derive from <code>SCRATCH</code> or some other light-weight image such as <code>alpine</code> (Dockerfile)

Add the binary back in

Push a tiny image to the Docker Hub

This normally meant having two separate Dockerfiles and a shell script to orchestrate all of the 7 steps above.

Here's an example from my href-counter repository which is a Golang application used to count the internal/external anchor tags on a web-page.

I'll provide all the files so you can see how much extra work was needed to get a small Docker image. Underneath I'll show the new format.

Dockerfile.build

  

Dockerfile

build.sh

 <code> </code>

Multi-stage builds give the benefits of the builder pattern without the hassle of maintaining three separate files:

This is huge for developers and maintainers, especially when you support multiple Dockerfiles for different architectures such as the Raspberry Pi.

The general syntax involves adding <code>FROM</code> additional times within your Dockerfile - whichever is the last <code>FROM</code> statement is the final base image.

To copy artifacts and outputs from intermediate images use <code>COPY --from=&lt;base_image_number&gt;</code>

The second PR mentioned improves on this syntax and when merged would mean you can do something more like:

Build Docker from master

You can create a development build of Docker at any time by cloning the docker/docker repository and typing in <code>make tgz</code>.

The resulting build will create binaries for you in the <code>bundles</code> folder.

Here's the build steps:

Let's try the example

Launch Docker within the container you built above:

These steps prepare the new Docker version for use:

The Docker development build creates an image called <code>docker-dev</code>. You can actually run Docker inside this image, which is what we'll do below: 

Now still within the container, clone my repository and initiate a build using the multi-step Dockefile:

the <code>-f</code> flag allows you to specify the name of a different Dockerfile.

Now run the Docker image:

 

Compare the differences in size between the resulting image and what we would have had if we used <code>FROM golang</code>:

Wrapping up

The builder pattern was effective as a work-around and would have created a binary of a similar size, but it was hard to maintain and very hard to use with Docker's automated build system.

If you need a small image - you should follow the builder pattern for now using the example above.

Once the feature is released through the stable channel and made available for auto-builds on the Hub/Cloud I would switch over.

Follow me on Twitter for more Docker news and tutorials.

Here's a quick blog showing how Multi-stage builds supersede the Builder pattern in @docker with @tonistiigi - https://t.co/r4mxzbCVzH

FaaS - a portable serverless framework for Docker

Tutorial: Monitor your applications with Prometheus

Running ad-hoc containers on Docker Swarm

Update:

If you'd like to save time building Docker on your own machine, I've submitted a lab to birthday.play-with-docker.com (which runs Docker in a webpage, with the master build) in the Intermediate section:

Docker Birthday labs: Intermediate section