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 theFROM
line. Specify thename
from thecontainer_pull
rule added to theWORKSPACE
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 DockerCOPY
config. Instead, you can useworkdir
to globally change the working directory or use asymlink
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 = "/",
)
-
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)
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"
},
)