Docker support for Bazel

Overview

These build rules are used for building Docker images. Such images are easy to modify and deploy system image for deploying application easily on cloud providers.

As traditional Dockerfile-based docker builds effectively execute a series of commands inside of Docker containers, saving the intermediate results as layers; this approach is unsuitable for use in Bazel for a variety of reasons.

The docker_build rule constructs a tarball that is compatible with docker save/load, and creates a single layer out of each BUILD rule in the chain.

Basic Example

Consider the following BUILD file in //third_party/debian:

load("@bazel_tools//tools/build_defs/docker:docker.bzl", "docker_build")

filegroup(
    name = "ca_certificates",
    srcs = ["ca_certificates.deb"],
)

# Example when you have all your dependencies in your repository.
# We have an example on how to fetch them from the web later in this
# document.
filegroup(
    name = "openjdk-7-jre-headless",
    srcs = ["openjdk-7-jre-headless.deb"],
)

docker_build(
    name = "wheezy",
    tars = ["wheezy.tar"],
)

The wheezy target in that BUILD file roughly corresponds to the Dockerfile:

FROM scratch
ADD wheezy.tar /

You can then build up subsequent layers via:

docker_build(
    name = "base",
    base = "//third_party/debian:wheezy",
    debs = ["//third_party/debian:ca_certificates"],
)

docker_build(
    name = "java",
    base = ":base",
    debs = ["//third_party/debian:openjdk-7-jre-headless"],
)

Image Configuration

You can set image configuration on these same rules by simply adding (supported) arguments to the rule, for instance:

docker_build(
    name = "my-layer",
    entrypoint = ["foo", "bar", "baz"],
    ...
)

Will have a similar effect as the Dockerfile construct:

ENTRYPOINT ["foo", "bar", "baz"]

For the set of supported configuration options see here

Using

Suppose you have a docker_build target //my/image:helloworld:

docker_build(
    name = "helloworld",
    ...
)

You can build this with bazel build my/image:helloworld.tar. This will produce the file bazel-genfiles/my/image/helloworld.tar. You can load this into my local Docker client by running docker load -i bazel-genfiles/my/image/helloworld.tar, or simply bazel run my/image:helloworld (this last command only update the changed layers and thus is faster).

Upon success you should be able to run docker images and see:

REPOSITORY          TAG                 IMAGE ID       ...
bazel/my_image      helloworld          d3440d7f2bde   ...

You can now use this docker image with the name bazel/my_image:helloworld or tag it with another name, for example: docker tag bazel/my_image:helloworld gcr.io/my-project/my-awesome-image:v0.9

You can do all that at once with specifying the tag on the command line of bazel run:

bazel run my/image:helloworld gcr.io/my-project/my-awesome-image:v0.9

Nota Bene: the docker images command will show a really old timestamp because docker_build removes all timestamps from the build to make it reproducible.

Pulling images and deb files from the internet

If you do not want to check in base image in your repository, you can use external repositories. For instance, you could create various layer with external labels:

load("@bazel_tools//tools/build_defs/docker:docker.bzl", "docker_build")

docker_build(
    name = "java",
    base = "@docker_debian//:wheezy",
    debs = ["@openjdk_7_jre_headless//file"],
)

Using the WORKSPACE file to add the actual files:

new_http_archive(
    name = "docker_debian",
    url = "https://codeload.github.com/tianon/docker-brew-debian/zip/e9bafb113f432c48c7e86c616424cb4b2f2c7a51",
    build_file = "debian.BUILD",
    type = "zip",
    sha256 = "515d385777643ef184729375bc5cb996134b3c1dc15c53acf104749b37334f68",
)

http_file(
   name = "openjdk_7_jre_headless",
   url = "http://security.debian.org/debian-security/pool/updates/main/o/openjdk-7/openjdk-7-jre-headless_7u79-2.5.5-1~deb7u1_amd64.deb",
   sha256 = "b632f0864450161d475c012dcfcc37a1243d9ebf7ff9d6292150955616d71c23",
)

With the following debian.BUILD file:

load("@bazel_tools//tools/build_defs/docker:docker.bzl", "docker_build")

# Extract .xz files
genrule(
    name = "wheezy_tar",
    srcs = ["docker-brew-debian-e9bafb113f432c48c7e86c616424cb4b2f2c7a51/wheezy/rootfs.tar.xz"],
    outs = ["wheezy_tar.tar"],
    cmd = "cat $< | xzcat >$@",
)

docker_build(
    name = "wheezy",
    tars = [":wheezy_tar"],
    visibility = ["//visibility:public"],
)

Future work

In the future, we would like to provide better integration with docker repositories: pull and push docker image.

docker_build

docker_build(name, base, data_path, directory, files, legacy_repository_naming, mode, tars, debs, symlinks, entrypoint, cmd, env, labels, ports, volumes, workdir, repository)