DEV Community

Brian Michalski
Brian Michalski

Posted on

Dockerfile to Bazel

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 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.

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 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 Packages1 docker.pkg.github.com

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 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).

$ 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

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 = "/",
)

  1. 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

Top comments (1)

Collapse
 
shankarvn profile image
shankarvn

Hi Brian,

Thank you for the post. I am still a little unsure whether the symlinks is going to work or not; do you have any examples for the symlinks bit?

This is what I have - My expectation is that the file output from a different target build (Angular UI build) //app-ui:myapp-ui and nginx.conf that I have in my source can be added to the correct location of my nginx image - Can I expect the distribution to get copied and served correctly from /usr/share/nginx/html

container_image(
name = "myapp-ui-image",
base = "@angular_nginx//image", # Pulled using container_pull of github.com/tiangolo/node-frontend
files = [
"//app-ui:myapp-ui", # Distribution generated by building the UI front end application
":nginx.conf" # Config for nginx sitting at the root directory
],
symlinks = {
"/usr/share/nginx/html": "//app-ui:myapp-ui",
"/etc/nginx/conf.d/default.conf": ":nginx.conf"
},
)