agentsclimarketplace

Bazel oci expert

Skill kinhluan/rules-quarkus-skills/.agent-skills/bazel-oci-expert

πŸ€– Complete AI expert ecosystem for Modern Java, Quarkus & Bazel development β˜•οΈβš‘οΈ Coverage for Vert.x, GraalVM, Maven/Gradle migration, and more πŸš€

Install
npx -y skills add kinhluan/rules-quarkus-skills --skill bazel-oci-expert

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Expert knowledge for building OCI container images with Bazel using rules_oci. Use for containerizing Quarkus/Java applications, multi-arch builds, and image optimization.

SKILL.md

12.8 KB, as published. Nobody here has run it

bazel-oci-expert

Keyword: oci | Platforms: gemini,claude,codex

Bazel OCI Container Expert Skill - Building, optimizing, and publishing OCI-compliant container images using rules_oci for Quarkus and Java applications.

Core Mandates

  • rules_oci First: Use rules_oci (not rules_docker) for OCI-compliant image builds. rules_docker is deprecated.
  • Hermetic Images: All image layers must be built hermetically within Bazel - no docker build.
  • Distroless Base: Prefer distroless or minimal base images (e.g., gcr.io/distroless/java21-debian12) for security.
  • Multi-Arch: Build for both AMD64 and ARM64 using platform transitions.
  • Layer Caching: Separate application code from dependencies to maximize layer cache hits.

Setup (Bzlmod)

MODULE.bazel

bazel_dep(name = "rules_oci", version = "2.0.0")

oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")

# Pull base images
oci.pull(
    name = "distroless_java21",
    digest = "sha256:abc123...",  # Pin by digest, not tag
    image = "gcr.io/distroless/java21-debian12",
    platforms = [
        "linux/amd64",
        "linux/arm64",
    ],
)

oci.pull(
    name = "ubi_minimal",
    digest = "sha256:def456...",
    image = "registry.access.redhat.com/ubi9/ubi-minimal",
    platforms = [
        "linux/amd64",
        "linux/arm64",
    ],
)

use_repo(oci, "distroless_java21", "ubi_minimal")

WORKSPACE.bazel (Legacy - avoid if using Bzlmod)

load("@rules_oci//oci:pull.bzl", "oci_pull")

oci_pull(
    name = "distroless_java21",
    digest = "sha256:abc123...",
    image = "gcr.io/distroless/java21-debian12",
)

Building Container Images

Basic Java Application Image

# services/user/BUILD.bazel
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_tarball")
load("@rules_pkg//pkg:tar.bzl", "pkg_tar")

# Package the JAR into a tar layer
pkg_tar(
    name = "app_layer",
    srcs = [":user-service_deploy.jar"],  # java_binary with deploy_env
    package_dir = "/app",
)

# Package dependencies into a separate layer
pkg_tar(
    name = "deps_layer",
    srcs = [":user-service-deps.jar"],
    package_dir = "/app/lib",
)

# Build the OCI image
oci_image(
    name = "user-service_image",
    base = "@distroless_java21",
    tars = [
        ":deps_layer",
        ":app_layer",
    ],
    entrypoint = ["/usr/bin/java", "-jar", "/app/user-service_deploy.jar"],
    env = {
        "JAVA_TOOL_OPTIONS": "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0",
    },
)

# Create a tarball for docker load
oci_tarball(
    name = "user-service_tar",
    image = ":user-service_image",
    repo_tags = ["user-service:latest"],
)

Quarkus Application Image

# services/order/BUILD.bazel
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_tarball")
load("@rules_pkg//pkg:tar.bzl", "pkg_tar")

# Layer 1: Quarkus dependencies (rarely changes)
pkg_tar(
    name = "quarkus_deps_layer",
    srcs = [":order-service-quarkus-app/lib"],
    package_dir = "/app/lib",
)

# Layer 2: Quarkus app (changes frequently)
pkg_tar(
    name = "quarkus_app_layer",
    srcs = [
        ":order-service-quarkus-app/quarkus-run.jar",
        ":order-service-quarkus-app/app",
    ],
    package_dir = "/app",
)

# Layer 3: Native libraries (if using JNI)
pkg_tar(
    name = "native_libs_layer",
    srcs = glob(["src/main/resources/*.so"]),
    package_dir = "/app/native",
)

oci_image(
    name = "order-service_image",
    base = "@ubi_minimal",
    tars = [
        ":quarkus_deps_layer",
        ":quarkus_app_layer",
        ":native_libs_layer",
    ],
    entrypoint = ["java", "-jar", "/app/quarkus-run.jar"],
    env = {
        "QUARKUS_HTTP_HOST": "0.0.0.0",
        "QUARKUS_HTTP_PORT": "8080",
        "JAVA_TOOL_OPTIONS": "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager",
    },
    exposed_ports = ["8080/tcp"],
)

oci_tarball(
    name = "order-service_tar",
    image = ":order-service_image",
    repo_tags = [
        "order-service:latest",
        "order-service:v1.0.0",
    ],
)

Native Image Container

# services/payment/BUILD.bazel
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_tarball")
load("@rules_pkg//pkg:tar.bzl", "pkg_tar")

# Native binary layer
pkg_tar(
    name = "native_binary_layer",
    srcs = [":payment-service-native"],  # GraalVM native binary
    package_dir = "/app",
)

# Native image is much smaller - use scratch or distroless
oci_image(
    name = "payment-service_image",
    base = "@distroless_static",  # Static binary base (no libc needed)
    tars = [":native_binary_layer"],
    entrypoint = ["/app/payment-service-native"],
    env = {
        "QUARKUS_HTTP_HOST": "0.0.0.0",
        "QUARKUS_HTTP_PORT": "8080",
    },
    exposed_ports = ["8080/tcp"],
)

oci_tarball(
    name = "payment-service_tar",
    image = ":payment-service_image",
    repo_tags = ["payment-service:native"],
)

Multi-Architecture Images

Platform-Specific Image Build

# platforms/BUILD.bazel (from bazel-expert)
platform(
    name = "linux_amd64",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
)

platform(
    name = "linux_arm64",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:arm64",
    ],
)
# services/user/BUILD.bazel
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index")

# AMD64 image
oci_image(
    name = "user-service_image_amd64",
    base = "@distroless_java21_linux_amd64",
    tars = [":app_layer"],
    entrypoint = ["/usr/bin/java", "-jar", "/app/user-service.jar"],
)

# ARM64 image
oci_image(
    name = "user-service_image_arm64",
    base = "@distroless_java21_linux_arm64",
    tars = [":app_layer"],
    entrypoint = ["/usr/bin/java", "-jar", "/app/user-service.jar"],
)

# Multi-arch index (manifest list)
oci_image_index(
    name = "user-service_image",
    images = [
        ":user-service_image_amd64",
        ":user-service_image_arm64",
    ],
)

Building with Platform Flag

# Build for specific platform
bazel build //services/user:user-service_image_amd64 \
  --platforms=//platforms:linux_amd64

# Build for ARM64
bazel build //services/user:user-service_image_arm64 \
  --platforms=//platforms:linux_arm64

# Build multi-arch index
bazel build //services/user:user-service_image

Image Optimization

Layer Ordering (Most Stable First)

oci_image(
    name = "optimized_image",
    base = "@distroless_java21",  # Layer 0: Base (rarely changes)
    tars = [
        ":system_deps_layer",     # Layer 1: System deps (rarely changes)
        ":shared_libs_layer",     # Layer 2: Shared libraries (occasionally changes)
        ":app_deps_layer",        # Layer 3: App dependencies (changes with version bumps)
        ":app_code_layer",        # Layer 4: Application code (changes frequently)
        ":config_layer",          # Layer 5: Config files (changes per environment)
    ],
)

JLink for Smaller Images

# Create custom JRE with only needed modules
load("@rules_java//java:defs.bzl", "java_runtime")

java_runtime(
    name = "custom_jre",
    srcs = [":jlink_output"],  # Output of jlink command
    java = "bin/java",
)

# Use custom JRE as base
oci_image(
    name = "minimal_java_image",
    base = ":custom_jre_image",
    tars = [":app_layer"],
)

Spring Boot / Quarkus Layered JARs

# For Quarkus fast-jar layout
pkg_tar(
    name = "quarkus_lib_layer",
    srcs = glob(["quarkus-app/lib/main/*.jar"]),
    package_dir = "/app/lib/main",
)

pkg_tar(
    name = "quarkus_app_layer",
    srcs = [
        "quarkus-app/quarkus-run.jar",
        "quarkus-app/app/*.jar",
    ],
    package_dir = "/app",
)

oci_image(
    name = "quarkus_fastjar_image",
    base = "@distroless_java21",
    tars = [
        ":quarkus_lib_layer",
        ":quarkus_app_layer",
    ],
    entrypoint = ["java", "-jar", "/app/quarkus-run.jar"],
)

Pushing Images

Using oci_push

load("@rules_oci//oci:defs.bzl", "oci_push")

oci_push(
    name = "push_user_service",
    image = ":user-service_image",
    remote_tags = ["latest", "v1.0.0", "{BUILD_TIMESTAMP}"],
    repository = "ghcr.io/myorg/user-service",
)
# Push to registry
bazel run //services/user:push_user_service

# With authentication
CRANE_AUTH_USERNAME=$GITHUB_ACTOR \
CRANE_AUTH_PASSWORD=$GITHUB_TOKEN \
bazel run //services/user:push_user_service

Using crane (for advanced scenarios)

load("@rules_oci//oci:defs.bzl", "oci_tarball")

oci_tarball(
    name = "user-service_for_crane",
    image = ":user-service_image",
    repo_tags = ["ghcr.io/myorg/user-service:latest"],
)
# Load and push with crane
crane push $(bazel cquery --output=files //services/user:user-service_for_crane) \
  ghcr.io/myorg/user-service:latest

Image Inspection & Testing

Structure Test

load("@rules_oci//oci:defs.bzl", "oci_image")

# Use container_structure_test for validation
load("@container_structure_test//:defs.bzl", "container_structure_test")

container_structure_test(
    name = "user-service_structure_test",
    image = ":user-service_image",
    configs = ["structure_test.yaml"],
)
# structure_test.yaml
schemaVersion: "2.0.0"
commandTests:
  - name: "java exists"
    command: "java"
    args: ["-version"]
    expectedError: ["openjdk version \"21"]

fileExistenceTests:
  - name: "app jar exists"
    path: "/app/user-service.jar"
    shouldExist: true

fileContentTests:
  - name: "correct entrypoint"
    path: "/app/user-service.jar"
    expectedContents: [".*"]

Image Size Check

# Check image size
bazel build //services/user:user-service_image
bazel run //tools:oci_inspect -- $(bazel cquery --output=files //services/user:user-service_image)

# Or use crane
crane manifest $(bazel cquery --output=files //services/user:user-service_image) | jq '.layers[] | .size' | awk '{sum+=$1} END {print sum/1024/1024 " MB"}'

Troubleshooting

"base image not found"

# Cause: oci.pull not properly configured in MODULE.bazel
# Fix: Ensure use_repo(oci, "image_name") is called
# Fix: Check digest is correct and image is accessible

"exec format error" in container

# Cause: Binary architecture doesn't match container architecture
# Fix: Build with correct --platforms flag
# Fix: Ensure base image matches target platform

Image too large

# Diagnosis: Check layer sizes
crane manifest ghcr.io/myorg/image:latest | jq '.layers[] | {size: .size, digest: .digest[:12]}'

# Fix: Separate deps and app code into different layers
# Fix: Use jlink to create minimal JRE
# Fix: Use distroless base instead of full OS
# Fix: Remove unnecessary files from layers

Push fails with authentication error

# Fix: Set CRANE_AUTH_USERNAME and CRANE_AUTH_PASSWORD
# Fix: Use docker login first if registry requires it
# Fix: Check repository URL is correct

Decision Trees

Choosing Base Image

Java application?
  β”œβ”€β”€ Need glibc? (most Java apps)
  β”‚     β”œβ”€β”€ Minimal size β†’ gcr.io/distroless/java21-debian12
  β”‚     └── Need shell/debug β†’ registry.access.redhat.com/ubi9/ubi-minimal
  β”œβ”€β”€ Static binary (GraalVM native)
  β”‚     └── gcr.io/distroless/static (or scratch)
  └── Need package manager
        └── alpine:latest (not recommended for Java - musl issues)

Layer Strategy

How often does this change?
  β”œβ”€β”€ Never (JDK, OS) β†’ Base image
  β”œβ”€β”€ Rarely (framework deps) β†’ Separate layer
  β”œβ”€β”€ Occasionally (app deps) β†’ Separate layer
  β”œβ”€β”€ Frequently (app code) β†’ Top layer
  └── Per environment (config) β†’ Topmost layer

References

Skill Interoperability

The bazel-oci-expert 🐳 skill provides containerization for:

  • rules-quarkus πŸ”§: Containerize Quarkus applications built with Bazel.
  • bazel-expert πŸ—: Core Bazel build infrastructure.
  • bazel-k8s-expert ☸️: Deploy containers to Kubernetes.
  • quarkus-expert ⚑: Quarkus-specific container configurations.
  • graalvm-expert πŸš€: Native image container builds.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.