Dockerfile to Bazel
Tags: bazel, docker, containers, devops
Published: 2020-05-31 21:36:02

I recently needed to build a Docker image as part of a large Bazel project I've been working on.  There are a handful of instructions out there how to take a binary built using bazel (like a `go_binary`) and convert it along into a container (`go_binary` =>`go_image` =>`container_image`) but it's less clear how to take a basic `Dockerfile` with someone else's image and add it to your project. Here's what to do:

# 1. Be prepared to throw out the `Dockerfile`.

Bazel's basic [Docker rules](https://github.com/bazelbuild/rules_docker) don't include any rules that let you just point at an existing Dockerfile and say build. Instead, we have to recreate the `Dockerfile` in our `BUILD.bazel`. Our existing Dockerfile will be a useful reference so don't throw it out quite yet, but if you've gotten attached to it now would be a good time to say goodbye.

For our example, let's convert the following Dockerfile which takes an existing Docker image, adds a file to it, and sets a command line flag.
```Docker
FROM bamnet/bqproxy

COPY queries.yaml queries.yaml
CMD ["--project=your-project-id"]
```

# 2. Add remote dependencies.

Anything that Bazel needs to download remotely need to be specified in a top-level `WORKSPACE` config. In this case, we have to add the base image specified in the `FROM` line to our `WORKSPACE` config since the base image lives on a container registry and not in our project.

```
load("@io_bazel_rules_docker//container:container.bzl", "container_pull")

container_pull(
    name = "bqproxy_latest",
    registry = "index.docker.io",
    repository = "bamnet/bqproxy",
    tag = "latest",
)
```
This will expose a `@bqproxy_latest//image` item that I can reference later. Like other bazel rules, the `name` field can contain whatever I want to refer to the image by elsewhere using the `@<name>//image` format.

Note that my existing Dockerfile didn't specify the registry - it defaulted to [Docker Hub](https://hub.docker.com/) automatically. Bazel doesn't like to make assumptions like this, you need to specify a registry path.

| Platform                  | registry              |
|---------------------------|-----------------------|
| Docker Hub                | index.docker.io       |
| Google Container Registry | gcr.io                |
| Gitlab Container Registry | registry.gitlab.com   |
| Github Packages[^github]  | docker.pkg.github.com |

[^github]: As of May 2020, Github Packages requires authentication even for public packages. In order to use them in your Bazel project you'll need to [configure authentication](https://github.com/bazelbuild/rules_docker#authentication).

# 3. Rebuild your `Dockerfile` as a `container_image`.

Bazel uses the `container_image` rule to build Docker images. Add one to the appropriate `BUILD.bazel` file for your project.

```
load("@io_bazel_rules_docker//container:container.bzl", "container_image")

container_image(
    name = "my_image",
    // TODO: Figure this out.
)
```

To replicate our earlier Dockerfile we need to set several options:

* `base` corresponds to the `FROM` line.  Specify the `name` from the `container_pull` rule added to the `WORKSPACE` file.  In this example, `@bqproxy_latest//image`.
* `cmd` specifies command-line options, it can take a string or an array.
* `files` specifies files which should be copied into the image. All files will be added to the current working directory. *You don't have the ability to set the destination for files being imported like you can using a Docker `COPY` config.* Instead, you can use `workdir` to globally change the working directory or use a `symlink` rule to map files into different locations of the file system.

See the [container_image](https://github.com/bazelbuild/rules_docker#container_image-1) docs for the full set of options.

# 4. Test it out!

Instead of running `docker build .` to build a local docker image, use `bazel run :my_image` (substitute the container_image name for my_image).

```sh
$ bazel run :my_image
INFO: Build completed successfully, 1 total action
Loaded image ID: sha256:b5da9bf97f1cdae8c66a775344d3c5ec99002a753973505aa1301bfb0f0b2649
Tagging b5da9bf97f1cdae8c66a775344d3c5ec99002a753973505aa1301bfb0f0b2649 as bazel/server:my_image
```

This will build a Docker image and display it's output path, like `bazel/server:my_image` which you can run just like any other docker image.  I used `docker run -ti --rm bazel/server:my_image` but you may need to pass additional args.

------

Our final conversion looks like:

**Source Dockerfile**
```Docker
FROM bamnet/bqproxy

COPY queries.yaml queries.yaml
CMD ["--project=your-project-id"]
```

turned into

**Target WORKSPACE**
```
load("@io_bazel_rules_docker//container:container.bzl", "container_pull")

container_pull(
    name = "bqproxy_latest",
    registry = "index.docker.io",
    repository = "bamnet/bqproxy",
    tag = "latest",
)
```

**Target BUILD.bazel**
```
load("@io_bazel_rules_docker//container:container.bzl", "container_image")

container_image(
    name = "my_image",
    base = "@bqproxy_latest//image",
    cmd = ["--project=your-project-id"],
    files = [
        "//server:queries.yaml",
    ],
    workdir = "/",
)
```