原文链接

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.

What was the builder pattern?

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.

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
golang 1.7.3 ef15416724f6 4 months ago 672MB

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:

  1. Derive from a Golang base image with the whole runtime/SDK (Dockerfile.build)
  2. Add source code
  3. Produce a statically-linked binary
  4. Copy the static binary from the image to the host (docker create, docker cp)
  5. Derive from SCRATCH or some other light-weight image such as alpine (Dockerfile)
  6. Add the binary back in
  7. 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.

Example

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

FROM golang:1.7.3

WORKDIR /go/src/github.com/alexellis/href-counter/

RUN go get -d -v golang.org/x/net/html
COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

  


Dockerfile

FROM alpine:latest
RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY app . CMD ["./app"]

  


build.sh

#!/bin/sh
echo Building alexellis2/href-counter:build docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \
-t alexellis2/href-counter:build . -f Dockerfile.build docker create --name extract alexellis2/href-counter:build
docker cp extract:/go/src/github.com/alexellis/href-counter/app ./app
docker rm -f extract echo Building alexellis2/href-counter:latest docker build --no-cache -t alexellis2/href-counter:latest .

  

 

What are multi-stage builds?

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

FROM golang:1.7.3

WORKDIR /go/src/github.com/alexellis/href-counter/

RUN go get -d -v golang.org/x/net/html
COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest
RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/alexellis/href-counter/app . CMD ["./app"]

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 FROM additional times within your Dockerfile - whichever is the last FROM statement is the final base image.

To copy artifacts and outputs from intermediate images use COPY --from=<base_image_number>

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

FROM golang:1.7.3 as builder

WORKDIR /go/src/github.com/alexellis/href-counter/

RUN go get -d -v golang.org/x/net/html
COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest
RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /go/src/github.com/alexellis/href-counter/app . CMD ["./app"]

  

How can I try it out?

Build Docker from master

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

The resulting build will create binaries for you in the bundles folder.

Here's the build steps:

$ git clone https://github.com/docker/docker
$ cd docker
$ make tgz

  

tgz其实就是tar.gz文件的简写,二者的格式没什么区别
解压命令通常如下
$ tar xvf 压缩文件名
x : 解压缩
v : 动作显示,显示出每个解压出来的文件,如果去掉该参数解压过程会变得快些,只是不显示动作而已
f : 文件 f后面一定跟着压缩文件的名称,例如a.tgz或b.tar.gz等

Let's try the example

Launch Docker within the container you built above:

  • These steps prepare the new Docker version for use:
$ docker run -v `pwd`/bundles:/go/src/github.com/docker/docker/bundles --privileged -ti docker-dev:master bash

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

$ export PATH=$PATH:`pwd`/bundles/latest/dynbinary-daemon:`pwd`/bundles/latest/binary-client/
$ dockerd &
  • Now still within the container, clone my repository and initiate a build using the multi-step Dockefile:
$ git clone https://github.com/alexellis/href-counter
$ cd href-counter
$ docker build -t href-counter . -f Dockerfile.multi

the -f flag allows you to specify the name of a different Dockerfile.

Now run the Docker image:

$ docker run -e url=https://www.alexellis.io/ multi
{"internal":9,"external":5} $ docker run -e url=https://www.docker.com multi
{"internal":97,"external":38}

 

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

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

multi               latest              bcbbf69a9b59        6 minutes ago       10.3MB
golang 1.7.3 ef15416724f6 4 months ago 672MB

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

— Alex Ellis (@alexellisuk) March 24, 2017

Recent blog posts:

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 Builders:Builder pattern vs. Multi-stage builds in Docker的更多相关文章

  1. 【原】iOS设计模式之:建造者模式Builder Pattern,用于改进初始化参数

    本文主要讨论一下iOS中的Builder Pattern.与网上很多版本不同,本文不去长篇大论地解释建造者模式的概念,那些东西太虚了.设计模式这种东西是为了解决实际问题的,不能为了设计模式而设计模式, ...

  2. .NET设计模式(4):建造者模式(Builder Pattern)

    ):建造者模式(Builder Pattern)    .建造者模式的使用使得产品的内部表象可以独立的变化.使用建造者模式可以使客户端不必知道产品内部组成的细节. 2.每一个Builder都相对独立, ...

  3. 二十四种设计模式:建造者模式(Builder Pattern)

    建造者模式(Builder Pattern) 介绍将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 示例用同样的构建过程创建Sql和Xml的Insert()方法和Get()方 ...

  4. iOS设计模式之:建造者模式Builder Pattern,用于改进初始化参数

    转自:http://www.cnblogs.com/wengzilin/p/4365855.html 本文主要讨论一下iOS中的Builder Pattern.与网上很多版本不同,本文不去长篇大论地解 ...

  5. C#设计模式:建造者模式(Builder Pattern)

    一,建造者模式(Builder Pattern) using System; using System.Collections.Generic; using System.Linq; using Sy ...

  6. 深入浅出设计模式——建造者模式(Builder Pattern)

    模式动机无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮.方向盘.发送机等各种部件.而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单 ...

  7. [转]C#设计模式(8)-Builder Pattern

    一. 建造者(Builder)模式 建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象. 对象性质的建造 有些情况下,一个对象会有一些重 ...

  8. Net设计模式实例之建造者模式(Builder Pattern)

    一.建造者模式简介(Brief Introduction) 建造者模式(Builder Pattern),将一个复杂对象的构建与它的表示分离,使的同样的构建过程可以创建不同的表示. 建造者模式的优点是 ...

  9. 设计模式(五)建造者模式(Builder Pattern)

    一.引言 在软件系统中,有时需要创建一个复杂对象,并且这个复杂对象由其各部分子对象通过一定的步骤组合而成.例如一个采购系统中,如果需要采购员去采购一批电脑时,在这个实际需求中,电脑就是一个复杂的对象, ...

随机推荐

  1. 【Hive学习之七】Hive 运行方式&权限管理

    环境 虚拟机:VMware 10 Linux版本:CentOS-6.5-x86_64 客户端:Xshell4 FTP:Xftp4 jdk8 hadoop-3.1.1 apache-hive-3.1.1 ...

  2. neuFlow&CNP-卷积计算加速器&神经网络加速芯片生态系统

    上周看到韩松毕业论文,扯出神经网络加速器EIE,刚好这周调研了一下neuFlow,扯出09年的一篇做卷积加速的文章,大牛Lecun Yan的学生做的,一晃眼,快十年了.也记录之. 这一套还没研究透,又 ...

  3. 微信小程序制作家庭记账本之四

    第四天,仍然对记账本代码进行研究,对按钮的大小设置,颜色,具体位置进行分析,但其中很多代码都不明白.

  4. Linux基础命令---文本格式转换expand,unexpand

    expand 将文件中的tab转换成空格,结果送到标准输出.如果没有指定文件,那么从标准输入读取. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.SUSE.openSUSE.F ...

  5. usdt钱包开发,比特币协议 Omni 层协议 USDT

    usdt钱包开发 比特币协议 -> Omni 层协议 -> USDT USDT是基于比特币omni协议的一种代币: https://omniexplorer.info/asset/31 I ...

  6. P1012 拼数

    P1012 拼数 输入输出样例 输入样例 3 13 312 343 输出样例 34331213 注意 当你输入: 6321 32 407 135 13 217 应该输出: 40732321217135 ...

  7. TensorFlow for distributed

    TensorFlow for distributed 本目录包括了运行时分布式TensorFlow的实现,其底层使用了gRPC 作为进程内通信的支持库. Quick start 首先,需要构建一个Te ...

  8. 前端框架VUE----模板字符串

    传统的JavaScript语言,输出模板通常是这样的写的. 1 $('#result').append( 2 'There are <b>' + basket.count + '</ ...

  9. checkbox 全选效果

    html部分 <p id="all">全选</p> <input type="checkbox" /><br/> ...

  10. 详解BOM头以及去掉BOM头的方法--踩过BOM的大坑

    类似WINDOWS自带的记事本等软件,在保存一个以UTF-8编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即BOM).它是一串隐藏的字符,用于让记事本等编辑器识别 ...