Let’s follow a typical journey to get our code into a development or staging environment. We’ll create a docker (OCI) container image, run it locally and push it to a cloud.
Hinting to our BUILD generator
Our example repo contains a power feature of the Aspect CLI. The file .aspect/cli/py3_image.star
is a Starlark extension of the BUILD file generator, which teaches it a simple Regex to perform:
RegexQuery(filter = "__main__.py", expression = """#\\s*oci:\\s*build""")
This means we could place a comment in our app/__main__.py
file like # oci:build
anywhere in the file to trigger the extension. After adding that, run bazel configure
again, and you’ll see a py3_image
target named image
is added to the BUILD file.
Run the image
The target that was generated can be built, to produce the OCI image. However most devs expect that a container image is also loaded into the daemon as part of building it, since that’s how docker build
works. To have a similar workflow, the target is also runnable, and will simply call docker load
for you.
Let’s query again to see what targets are available:
% bazel query --output=label_kind app:all
py_library rule //app:__test__
...
tar rule //app:_image_layers_default
tar rule //app:_image_layers_interpreter
genrule rule //app:_image_layers_manifests
tar rule //app:_image_layers_packages
py_binary rule //app:app_bin
py_venv_rule rule //app:app_bin.venv
py_test rule //app:app_test
py_venv_rule rule //app:app_test.venv
platform_transition_filegroup rule //app:image
oci_load rule //app:image.load
oci_image rule //app:image_image
_jq_rule rule //app:image_image.digest
filegroup rule //app:image_layers
mtree_spec rule //app:image_layers.manifest
oci_load
is the rule kind we want, so let’s run bazel run //app:image.load
The .load
suffix on the label is a convention for a “custom command” in Bazel, so you can imagine it’s similar to typing bazel load //app:image
The command prints the registry and tag that was loaded: Loaded image: localhost/app:latest
That means we can now run it with our container runtime:
% docker run --rm -it localhost/app:latest
_______________________________________
/ \
| -- Built at <unstamped> -- |
| I'm a cow, I found this on the internet |
| <!DOCTYPE html> |
| <html> |
...
Stamping artifacts
Let’s say we deploy our container to prod, and the monitoring system pages us because it’s crash-looping. We’ll want to figure out whether the crashes are on our new version - but we still have <unstamped>
in the program.
When we build artifacts for pushing to an environment, ideally in a continuous delivery step on CI, we are willing to give up some of the determinism guarantee. It’s okay to have some cache misses after we’ve gotten green test results. Bazel has a --stamp
flag which means that the outputs can intentionally be non-deterministic and use data from VCS.
So we simply run
% bazel run --stamp app:image.load
% docker run --rm -it localhost/app:latest
_______________________________________
/ \
| -- Built at 1737154064 -- |
| I'm a cow, I found this on the internet |
| <!DOCTYPE html> |
Now we see the header template was re-built with a current timestamp, then the application and the container image updated as well.