Build images with BuildKit

Estimated reading time: 10 minutes

Docker Build is one of the most used features of the Docker Engine - users ranging from developers, build teams, and release teams all use Docker Build.

Docker Build enhancements for 18.09 release introduces a much-needed overhaul of the build architecture. By integrating BuildKit, users should see an improvement on performance, storage management, feature functionality, and security.

  • Docker images created with BuildKit can be pushed to Docker Hub just like Docker images created with legacy build
  • the Dockerfile format that works on legacy build will also work with BuildKit builds
  • The new --secret command line option allows the user to pass secret information for building new images with a specified Dockerfile

For more information on build options, see the reference guide on the command line build options and the Dockerfile reference page.


  • A current version of Docker (18.09 or higher)
  • Network connection required for downloading images of custom frontends


  • Only supported for building Linux containers

To enable BuildKit builds

Easiest way from a fresh install of docker is to set the DOCKER_BUILDKIT=1 environment variable when invoking the docker build command, such as:

$ DOCKER_BUILDKIT=1 docker build .

To enable docker BuildKit by default, set daemon configuration in /etc/docker/daemon.json feature to true and restart the daemon:

{ "features": { "buildkit": true } }

New Docker Build command line build output

New docker build BuildKit TTY output (default):

$ docker build . 

[+] Building 70.9s (34/59)                                                      
 => [runc 1/4] COPY hack/dockerfile/install/ ./       14.0s
 => [frozen-images 3/4] RUN / /build  buildpa  24.9s
 => [containerd 4/5] RUN PREFIX=/build/ ./ containerd           37.1s
 => [tini 2/5] COPY hack/dockerfile/install/ ./        4.9s
 => [vndr 2/4] COPY hack/dockerfile/install/vndr.installer ./              1.6s
 => [dockercli 2/4] COPY hack/dockerfile/install/dockercli.installer ./    5.9s
 => [proxy 2/4] COPY hack/dockerfile/install/proxy.installer ./           15.7s
 => [tomlv 2/4] COPY hack/dockerfile/install/tomlv.installer ./           12.4s
 => [gometalinter 2/4] COPY hack/dockerfile/install/gometalinter.install  25.5s
 => [vndr 3/4] RUN PREFIX=/build/ ./ vndr                       33.2s
 => [tini 3/5] COPY hack/dockerfile/install/tini.installer ./              6.1s
 => [dockercli 3/4] RUN PREFIX=/build/ ./ dockercli             18.0s
 => [runc 2/4] COPY hack/dockerfile/install/runc.installer ./              2.4s
 => [tini 4/5] RUN PREFIX=/build/ ./ tini                       11.6s
 => [runc 3/4] RUN PREFIX=/build/ ./ runc                       23.4s
 => [tomlv 3/4] RUN PREFIX=/build/ ./ tomlv                      9.7s
 => [proxy 3/4] RUN PREFIX=/build/ ./ proxy                     14.6s
 => [dev 2/23] RUN useradd --create-home --gid docker unprivilegeduser     5.1s
 => [gometalinter 3/4] RUN PREFIX=/build/ ./ gometalinter        9.4s
 => [dev 3/23] RUN ln -sfv /go/src/ ~/.ba  4.3s
 => [dev 4/23] RUN echo source /usr/share/bash-completion/bash_completion  2.5s
 => [dev 5/23] RUN ln -s /usr/local/completion/bash/docker /etc/bash_comp  2.1s

New docker build BuildKit plain output:

$ docker build --progress=plain . 

#1 [internal] load .dockerignore
#1       digest: sha256:d0b5f1b2d994bfdacee98198b07119b61cf2442e548a41cf4cd6d0471a627414
#1         name: "[internal] load .dockerignore"
#1      started: 2018-08-31 19:07:09.246319297 +0000 UTC
#1    completed: 2018-08-31 19:07:09.246386115 +0000 UTC
#1     duration: 66.818µs
#1      started: 2018-08-31 19:07:09.246547272 +0000 UTC
#1    completed: 2018-08-31 19:07:09.260979324 +0000 UTC
#1     duration: 14.432052ms
#1 transferring context: 142B done

#2 [internal] load Dockerfile
#2       digest: sha256:2f10ef7338b6eebaf1b072752d0d936c3d38c4383476a3985824ff70398569fa
#2         name: "[internal] load Dockerfile"
#2      started: 2018-08-31 19:07:09.246331352 +0000 UTC
#2    completed: 2018-08-31 19:07:09.246386021 +0000 UTC
#2     duration: 54.669µs
#2      started: 2018-08-31 19:07:09.246720773 +0000 UTC
#2    completed: 2018-08-31 19:07:09.270231987 +0000 UTC
#2     duration: 23.511214ms
#2 transferring dockerfile: 9.26kB done

Overriding default frontends

The new syntax features in Dockerfile are available if you override the default frontend. To override the default frontend, set the first line of the Dockerfile as a comment with a specific frontend image:

# syntax=<frontend image>, e.g. # syntax=docker/dockerfile:1.2

The examples on this page use features that are available in docker/dockerfile version 1.2.0 and up. We recommend using docker/dockerfile:1, which always points to the latest release of the version 1 syntax. BuildKit automatically checks for updates of the syntax before building, making sure you are using the most current version. Learn more about the syntax directive in the Dockerfile reference.

New Docker Build secret information

The new --secret flag for docker build allows the user to pass secret information to be used in the Dockerfile for building docker images in a safe way that will not end up stored in the final image.

id is the identifier to pass into the docker build --secret. This identifier is associated with the RUN --mount identifier to use in the Dockerfile. Docker does not use the filename of where the secret is kept outside of the Dockerfile, since this may be sensitive information.

dst renames the secret file to a specific file in the Dockerfile RUN command to use.

For example, with a secret piece of information stored in a text file:

$ echo 'WARMACHINEROX' > mysecret.txt

And with a Dockerfile that specifies use of a BuildKit frontend docker/dockerfile:1.2, the secret can be accessed when performing a RUN:

# syntax=docker/dockerfile:1.2

FROM alpine

# shows secret from default secret location:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

# shows secret from custom secret location:
RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar

The secret needs to be passed to the build using the --secret flag. This Dockerfile is only to demonstrate that the secret can be accessed. As you can see the secret printed in the build output. The final image built will not have the secret file:

$ docker build --no-cache --progress=plain --secret id=mysecret,src=mysecret.txt .
#8 [2/3] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#8       digest: sha256:5d8cbaeb66183993700828632bfbde246cae8feded11aad40e524f54ce7438d6
#8         name: "[2/3] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret"
#8      started: 2018-08-31 21:03:30.703550864 +0000 UTC
#8    completed: 2018-08-31 21:03:32.051053831 +0000 UTC
#8     duration: 1.347502967s

#9 [3/3] RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar
#9       digest: sha256:6c7ebda4599ec6acb40358017e51ccb4c5471dc434573b9b7188143757459efa
#9         name: "[3/3] RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar"
#9      started: 2018-08-31 21:03:32.052880985 +0000 UTC
#9    completed: 2018-08-31 21:03:33.523282118 +0000 UTC
#9     duration: 1.470401133s

Using SSH to access private data in builds


Please see Build secrets and SSH forwarding in Docker 18.09 for more information and examples.

Some commands in a Dockerfile may need specific SSH authentication - for example, to clone a private repository. Rather than copying private keys into the image, which runs the risk of exposing them publicly, docker build provides a way to use the host system’s ssh access while building the image.

There are three steps to this process.

First, run ssh-add to add private key identities to the authentication agent. If you have more than one SSH key and your default id_rsa is not the one you use for accessing the resources in question, you’ll need to add that key by path: ssh-add ~/.ssh/<some other key>. (For more information on SSH agent, see the OpenSSH man page.)

Second, when running docker build, use the --ssh option to pass in an existing SSH agent connection socket. For example, --ssh default=$SSH_AUTH_SOCK, or the shorter equivalent, --ssh default.

Third, to make use of that SSH access in a RUN command in the Dockerfile, define a mount with type ssh. This will set the SSH_AUTH_SOCK environment variable for that command to the value provided by the host to docker build, which will cause any programs in the RUN command which rely on SSH to automatically use that socket. Only the commands in the Dockerfile that have explicitly requested SSH access by defining type=ssh mount will have access to SSH agent connections. The other commands will have no knowledge of any SSH agent being available.

Here is an example Dockerfile using SSH in the container:

# syntax=docker/dockerfile:1
FROM alpine

# Install ssh client and git
RUN apk add --no-cache openssh-client git

# Download public key for
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan >> ~/.ssh/known_hosts

# Clone private repository
RUN --mount=type=ssh git clone myproject

The image could be built as follows:

$ docker build --ssh default .

As with --mount=type=secret, you can specify an id if you want to use multiple sockets per build and want to differentiate them. For example, you could run docker build --ssh main=$SSH_AUTH_SOCK --ssh other=$OTHER_SSH_AUTH_SOCK. In your Dockerfile, you could then have a RUN --mount=type=ssh,id=main and a RUN --mount=type=ssh,id=other to use those two sockets. If a --mount=type=ssh doesn’t specify an id, default is assumed.

Troubleshooting : issues with private registries

x509: certificate signed by unknown authority

If you are fetching images from insecure registry (with self-signed certificates) and/or using such a registry as a mirror, you are facing a known issue in Docker 18.09 :

[+] Building 0.4s (3/3) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 169B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => ERROR resolve image config for
 > resolve image config for
failed to do request: Head x509: certificate signed by unknown authority

Solution: secure your registry properly. You can get SSL certificates from Let’s Encrypt for free. See Deploy a registry server.

image not found when the private registry is running on Sonatype Nexus version < 3.15

If you are running a private registry using Sonatype Nexus version < 3.15, and receive an error similar to the following :

 > [internal] load metadata for
 > [1/4] FROM
rpc error: code = Unknown desc = not found

you may be facing the bug below : NEXUS-12684

Solution is to upgrade your Nexus to version 3.15 or above.

build, security, engine, secret, BuildKit