Define the image
aspect_rules_py — the same ruleset that provides the py_binary you’ve been using — also provides py_image_layer, which splits a py_binary into OCI layers (interpreter, third-party packages, and your code) so image rebuilds stay fast and cache-friendly. (Projects scaffolded with the oci feature ship a small py3_image macro that wraps exactly this; here we’ll wire it up directly to see the pieces.)
Add the container targets to app/BUILD.bazel. py_image_layer produces the layer tarballs, rules_oci’s oci_image assembles them onto a base image, and oci_load makes the result loadable into your local container runtime:
Run the image
You can build:image 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.
That’s what the oci_load target above is for — it’s runnable and calls the equivalent of docker load for you.
oci_load is the rule kind you want, so run bazel run //app:load
The command prints the registry and tag that was loaded: Loaded image: localhost/app:latest
That means you can now run it with your container runtime:
Stamping artifacts
Say you deploy the container to prod, and the monitoring system pages you because it’s crash-looping. You want to figure out whether the crashes are on the new version - but you still have<unstamped> in the program.
When you build artifacts for pushing to an environment, ideally in a continuous delivery step on CI, you are willing to give up some of the determinism guarantee. It’s okay to have some cache misses after you’ve got green test results. Bazel has a --stamp flag which means that the outputs can intentionally be non-deterministic and use data from VCS.
So run:

