Build and push container image using bazel
I am building a small golang webapp , and I want to push a container up for it, which can eventually be used in Google Cloud Run, or elsewhere.
In this post, I want to describe how I got it to push images build locally up to googles artifact repository. It will include creating the artifac repository using infrastructure as code
The project is in a monorepo that uses bazel .
The executable to be packaged
# //products/example/cmd/server/BUILD.bazel
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
go_library(
name = "server_lib",
srcs = ["main.go"],
visibility = ["//visibility:private"],
)
go_binary(
name = "server",
embed = [":server_lib"],
visibility = ["//visibility:public"],
)
Build Container Image
add rules_oci
rules_oci is a good place to start to integrate container support into your bazel configuration. It’s also worth reading up a bit on distroless if you are not aware of it.
Adding rules_oci into bazel MODULES is straightforward:
bazel_dep(name = "rules_oci", version = "2.2.6")
# For testing, we also recommend https://registry.bazel.build/modules/container_structure_test
oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
# Declare external images you need to pull, for example:
oci.pull(
name = "distroless_base",
# 'latest' is not reproducible, but it's convenient.
# During the build we print a WARNING message that includes recommended 'digest' and 'platforms'
# values which you can use here in place of 'tag' to pin for reproducibility.
tag = "latest",
image = "gcr.io/distroless/base",
platforms = ["linux/amd64"],
)
# For each oci.pull call, repeat the "name" here to expose them as dependencies.
use_repo(oci, "distroless_base")
If you use the WORKSPACE, or if you want the latest version, you
can find details on their releases page
.
tar.bzl
oci_rules uses tar files to build the image. To build tar images, you will
want to use tar.bzl
.
Add the following to you MODULES
bazel_dep(name = "tar.bzl", version = "0.3.0")
Latest version and instructions for WORKSPACE
can be found on their releases page
BUILD.bazel
There are language specific sample build files that you can start from.
Starting
from the example go BUILD.bazel
,
and simplifying it, I have:
# //products/example/deploy/BUILD.bazel
load("@rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load", "oci_push")
load("@tar.bzl", "mutate", "tar")
# Put app go_binary into a tar layer.
tar(
name = "app_layer",
srcs = ["//products/muster/cmd/server:server"],
out = "app_layer.tar",
mutate = mutate(strip_prefix = package_name() + "/app_"),
)
oci_image(
name = "image",
# This is defined by an oci.pull() call in /MODULE.bazel
base = "@distroless_base",
entrypoint = ["/app"],
# Link the resulting image back to the repository where the build is defined.
labels = {
"org.opencontainers.image.source": "https://github.com/aspect-build/bazel-examples",
},
tars = [":app_layer"],
)
There is
more information at the go doc page
including details of migrating from
rules_docker
| |
Building of the images now completes
Push Image
Pushing the image can be pretty straightforward:
- Enable the container repository in Google Cloud
- Configure it in the
BUILD.bazelfile bazel runto push
I would like to do the whole thing through IaC so that it is
- Repeatable, and
- more importantly documented
IaC Tool Options
terraform has no bazel support, and is a questionable choice from an ethics perspective. opentofu has
rules_tf
. However, that
does not support apply
Google Cloud Infrastucture Manager is vendor lock in.
That leaves us with pulumi (If there is another alternative, please let me know).
I like the stacks concept in pulumi, and while it doesn’t have bazel integration, what it does have is an automation api . While not he best documented, there is enough documentation out there to be able to piece it together.
With this, we can build a runnable unit, and then run it too. I’ve experimented with tagging specific runnables and then running them through the CI. I won’t get that far in this post. My focus here is to get it working locally.
Enable Artifact Repository
In pulumi, you can enable the artifact repository with:
| |
Authenticating against the repo
You need to authenticate against the repository so that the oci_rule can pick
it up
| |
Define the push target
oci_push(
name = "push",
image = ":image",
remote_tags = [
"latest",
"1h",
],
repository = "europe-west1-docker.pkg.dev/<projectId>/<repo-name>/<image-name>",
)
Pushing
Once all the above is set up, you can then push the image with one of the following:
| |
Side note
Please do not take my heavy usage of google and its products to be a fan letter or an endorsement. They might at best, be the lesser evil.