rules_oci

repository·main·Indexed 19 days ago

https://github.com/bazel-contrib/rules_oci

Bazel rules for creating and managing OCI (Open Container Initiative) compliant container images. It provides APIs for image construction (oci_image, oci_image_index), loading images into daemons (oci_load), and interacting with remote registries (oci_pull, oci_push). The ruleset focuses on simplicity and adherence to the OCI specification, offering alternatives to Dockerfiles for hermetic image builds, including support for multi-architecture images and developer preview features for image signing and attestation via cosign.

Tokens
16.5K
Snippets
55
Records
69
Agent score
59%

What's inside rules_oci

  1. Compare rules_oci with rules_img and rules_docker

    main

    vs rules_img

    rules_oci is designed for maintainability using standard container tools and DefaultInfo (passing files/directories between rules). This makes it simpler but less optimized for remote caching/execution compared to rules_img, as more bytes may be sent over the network. Use rules_img if you require high-performance remote execution.

    vs rules_docker

    rules_oci is not a complete replacement for rules_docker. While it covers most use cases, it lacks certain features like container_run_and* rules. If you are migrating from rules_docker, refer to the Aspect migration guide.

  2. Alternatives to rules_docker container run rules

    main

    The rules_docker rules container_run_and_commit, container_run_and_commit_layer, container_run_and_extract, and dockerfile_build are not supported in rules_oci because they are not hermetic and interact incorrectly with Bazel caching.

    Recommended Approaches:

    1. Build a base layer: Use an external pipeline (e.g., a Dockerfile) to build a base image, push it to a registry, and then use oci_pull in Bazel to fetch it and add application layers on top.
    2. Fetch packages hermetically: Use Bazel to construct reproducible packages outside of a container. Use oci_pull for a base image, then add layers containing system packages using specialized rules:
      • Alpine: Use rules_apko.
      • Debian: Use rules_debian_packages.
      • Distroless: See rules_distroless for recipes.
    3. Docker spawn strategy: Use Bazel's built-in capability to run build actions inside a container (see Bazel remote sandbox documentation).
  3. How oci_image works as a macro wrapper

    main

    Most users should use the oci_image macro instead of the oci_image_rule directly.

    oci_image is a wrapper that provides more flexible ways to define metadata. While oci_image_rule requires labels, annotations, and environment variables to be provided via files, oci_image allows you to provide them as either a file OR an inline dictionary (dict(key -> value)).

    Key features of the oci_image macro:

    • Flexible Configuration: Supports inline dictionaries for labels, annotations, env, cmd, entrypoint, exposed_ports, and volumes.
    • Digest Output: Automatically produces a target named [name].digest, which contains the sha256 digest of the resulting image.
    • Preprocessing Support: Files used for labels or environment variables can be preprocessed (e.g., using jq) to inject non-deterministic information when running Bazel with the --stamp flag.
    load("@rules_oci//oci:defs.bzl", "oci_image")
    
    oci_image(
        name = "my_image",
        labels = {"org.opencontainers.image.vendor": "my-company"},
        env = {"APP_COLOR": "blue"},
        cmd = ["/app/bin", "--arg"],
    )
  4. How to handle RUN instructions in rules_oci

    main

    The rules_oci ruleset does not provide a direct replacement for the RUN instruction. This is because RUN requires a running Container Daemon and is non-hermetic, which conflicts with Bazel's design principles.

    To achieve the effects of RUN (such as installing packages), you should use specialized rulesets designed for hermetic package management:

  5. Migrate container_image to oci_image

    main

    Replace container_image with oci_image.

    Key Changes:

    • layers is replaced by tars. oci_image#tars accepts a list of .tar or .tar.gz files and creates one layer per tar in the order provided.
    • Many attributes previously on container_image must now be handled by pkg_tar when creating the layers (tars) that are passed to oci_image:
      • files $\rightarrow$ pkg_tar#srcs
      • compression $\rightarrow$ pkg_tar#compressor
      • data_path $\rightarrow$ pkg_tar#strip_prefix
      • directory $\rightarrow$ pkg_tar#package_dir
      • symlinks $\rightarrow$ pkg_tar#symlinks
      • mode $\rightarrow$ pkg_tar#mode
    • launcher and launcher_args are unsupported. Use entrypoint and cmd instead.
    • ports and volumes are unsupported; these should be handled by the container runtime at startup.
    • debs are not supported directly. Extract .deb files into data.tar.xz and control.tar.xz and pass them to oci_image#tars via a genrule or similar.
    -container_image(
    +oci_image(
         name = "image",
    -    layers = [
    +    tars = [
             ":layer"
         ]
     )
  6. Pull a base image using oci_pull

    main

    Once you have identified the registry, repository, and digest of your desired base image, use oci_pull in your WORKSPACE file to make it available to rules_oci.

    load("@rules_oci//oci:pull.bzl", "oci_pull")
    
    oci_pull(
        name = "distroless_base",
        digest = "sha256:ccaef5ee2f1850270d453fdf700a5392534f8d1a8ca2acda391fbb6a06b81c86",
        image = "gcr.io/distroless/base",
        platforms = ["linux/amd64","linux/arm64"],
    )
  7. Package a C/C++ application into an OCI image

    main

    To package a C/C++ application using rules_oci, you must follow a multi-step pipeline:

    1. Define a cc_binary for your application.
    2. Wrap the binary in a tar rule, as oci_image requires tarballs as input.
    3. Use oci_image to compose the image, specifying a base image (e.g., Ubuntu or a distroless image) to provide necessary runtime libraries like glibc or libstdc++.
    4. Use oci_load to create a target that can load the image into a local container runtime.

    Note: C++ programs typically require fundamental libraries. It is recommended to use language-specific distroless images or a base image like docker.io/library/ubuntu to ensure these dependencies are present.

    load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load")
    load("@rules_cc//cc:defs.bzl", "cc_binary")
    load("@tar.bzl", "tar")
    
    package(default_visibility = ["//visibility:public"])
    
    # 1. The binary
    cc_binary(
        name  = "example_binary",
        srcs = ["example.cc"],
    )
    
    # 2. Wrap in tar
    tar(
        name = "tar",
        srcs = [":example_binary"],
    )
    
    # 3. Create the OCI image
    oci_image(
        name = "image",
        base = "@docker_lib_ubuntu",
        tars = [":tar"],
        entrypoint = ["/example_binary"],
    )
    
    # 4. Target to load the image
    oci_load(
        name = "image_load",
        image = ":image",
        repo_tags = ["example:latest"],
    )
  8. Migrate container_pull to oci_pull

    main

    When migrating from rules_docker to rules_oci, replace container_pull with oci_pull.

    Key Changes:

    • oci_pull uses Bazel's downloader instead of a custom puller binary. Consequently, puller_darwin and puller_linux_* are unsupported.
    • import_tags, cred_helpers, docker_client_config, os_version, os_features, and platform_features are unsupported.
    • To handle credentials, ensure credential helpers are installed on the host where Bazel runs. Use the DOCKER_CONFIG environment variable to override configuration.
    • Platform specifications (os, architecture, cpu_variant) are now combined into a single string passed to the platforms attribute.
    • timeout is unsupported; use Bazel's --http_timeout_scaling flag instead.
    • DOCKER_REPO_CACHE and PULLER_TIMEOUT are not supported. Remote manifests/blobs are cached via Bazel's repository cache if a digest is provided.
    -load("@io_bazel_rules_docker//container:container.bzl", "container_pull")
    +load("@rules_oci//oci:pull.bzl", "oci_pull")
    -container_pull(
    - os = "linux",
    - architecture = "arm64"
    - cpu_variant = "v8"
    +oci_pull(
    + platforms = [
    +     "linux/arm64/v8"
    + ]
    )
  9. Configure WORKSPACE for Scala and OCI images

    main

    When using rules_scala (which may not yet support Bzlmod), you must configure your WORKSPACE file to include rules_scala, aspect_bazel_lib (for the tar rule), and rules_oci.

    Key steps in WORKSPACE:

    • Initialize rules_scala with scala_config and scala_repositories.
    • Register aspect_bazel_lib toolchains to enable layer creation via tar.
    • Initialize rules_oci and register toolchains using oci_register_toolchains.
    • Use oci_pull to fetch base images like Distroless Java.
    load("@io_bazel_rules_scala//:scala_config.bzl", "scala_config")
    scala_config(scala_version = "2.13.12")
    
    load("@io_bazel_rules_scala//scala:scala.bzl", "scala_repositories")
    scala_repositories()
    
    load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies", "aspect_bazel_lib_register_toolchains")
    aspect_bazel_lib_dependencies()
    aspect_bazel_lib_register_toolchains()
    
    load("@rules_oci//oci:dependencies.bzl", "rules_oci_dependencies")
    rules_oci_dependencies()
    
    load("@rules_oci//oci:repositories.bzl", "LATEST_CRANE_VERSION", "oci_register_toolchains")
    oci_register_toolchains(
        name = "oci",
        crane_version = LATEST_CRANE_VERSION,
    )
    
    load("@rules_oci//oci:pull.bzl", "oci_pull")
    oci_pull(
        name = "distroless_java",
        digest = "sha256:161a1d97d592b3f1919801578c3a47c8e932071168a96267698f4b669c24c76d",
        image = "gcr.io/distroless/java17",
    )
  10. Enable containerd snapshotter on Linux

    main

    To enable the containerd-snapshotter feature on most Linux distributions, modify your Docker daemon configuration and restart the service.

    # 1. Add this to /etc/docker/daemon.json
    {
      "features": {
        "containerd-snapshotter": true
      }
    }
    
    # 2. Restart the daemon
    sudo systemctl restart docker
    
    # 3. Verify the driver status
    docker info -f '{{ .DriverStatus }}'
    # Expected output: [[driver-type io.containerd.snapshotter.v1]]
  11. Create images containing Python applications

    main

    When building OCI images for Python applications, the recommended pattern is to compose the image from three distinct layers to optimize caching and reuse:

    1. Interpreter layer: Contains the Python runtime.
    2. site-packages layer: Contains the installed dependencies.
    3. Application layer: Contains the actual application code.

    This approach is the modern replacement for the py3_image rule in rules_docker.

    https://github.com/aspect-build/bazel-examples/tree/main/oci_python_image