4.11 Ruleset Packaging & Publishing
The moment a user copies bazel_dep(name = "rules_foo", version = "1.2.3"), your ruleset stops being just a repository of Starlark files. It becomes a dependency contract. The user is trusting that the module name is stable, public .bzl files are the right ones to load, toolchains can be registered without understanding private repos, release bytes match registry metadata, and the next Bazel upgrade will not require guesswork.
Publishing is not the final upload step after rule authoring. It is where layout, setup APIs, module metadata, release artifacts, CI, documentation, and compatibility policy become one user-facing surface. A ruleset can have excellent implementations from 4.2 Custom Rules, Providers & Actions, a careful production surface from 4.4 Production Rule Surface, and proof layers from 4.5 Rule Testing & Documentation, yet still be painful to adopt if the published shape asks users to reverse-engineer how it is meant to be consumed.
The User's First Five Questions
The first question is "what do I load?" 4.11.1 Ruleset Layout & Public Entry Points starts with public entry points such as defs.bzl, setup files for Bzlmod or legacy WORKSPACE users, private paths that are clearly not supported load targets, examples from 4.5.3 Example Workspaces as Contract Tests, and generated docs from 4.5.4 Stardoc — API Documentation. Layout teaches users what is API and gives maintainers room to reorganize implementation files without breaking downstream load() statements.
The second question is "how do tools appear?" 4.11.2 Toolchainization applies the same contract thinking to compilers, SDKs, runtimes, and helper tools. A user should not have to list every platform archive, know every generated repository, or download a whole SDK matrix just so Bazel can inspect toolchain candidates. The pattern is a small setup API, a lightweight hub repo with toolchain() declarations, heavy artifacts behind implementation repos, and register_toolchains("@repo//...:all") connected to 4.6.3 Toolchain Resolution.
The third question is "what version did I add to my graph?" 4.11.3 Module Metadata & Version Semantics treats MODULE.bazel as public metadata, not project boilerplate. The module name is what users write in bazel_dep(). Dependency versions are minimum requirements. dev_dependency = True keeps build-only machinery out of downstream resolution. bazel_compatibility can check the current Bazel version, but it does not replace a human compatibility policy, and older compatibility_level guidance should not be the center of a new design.
The fourth question is "what bytes did the registry fetch?" 4.11.4 Publishing Rulesets moves from metadata to artifacts. BCR consumes a module version with MODULE.bazel, source.json, integrity hashes, optional patches or overlays, and presubmit configuration. Release archives, platform binaries, docs archives, and provenance data are all part of the fetchable supply chain. A tag is not enough. The exact artifact reachable from the registry has to be reproducible, verifiable, and shaped like the module users will actually build.
The fifth question is "can I keep upgrading?" 4.11.5 Ruleset CI/CD Patterns turns the published contract into evidence, and 4.11.6 Rule Compatibility & Bazel Version Policy names the promise CI is proving. Presubmit should exercise rule tests, integration fixtures, examples, generated docs, and setup paths from the README. The version and platform matrix should match the support promise, including old-enough Bazel, current or LTS Bazel, and a forward-looking lane when the policy calls for it. Tag workflows should rerun the release gate from the exact source being published before artifact and BCR publishing. The policy should cover .bzl entry points, attrs, providers, toolchain types, module-extension tags, generated repo names, default outputs, output groups, examples, docs, and supported Bazel versions, and it should avoid forcing users to upgrade both the ruleset major version and the Bazel major version in the same emergency.
Release Shape Is API Design
Earlier in Level 4, API design meant rules, providers, actions, macros, toolchains, repository rules, and module extensions. Section 4.11 widens that lens. A MODULE.bazel module name is an API because users type it into their dependency graph. A generated repo name is an API when docs tell users to use_repo() it. A defs.bzl file is an API boundary because every downstream load() bakes it into source. A source.json checksum is an API boundary because Bazel refuses to fetch bytes that do not match it. A CI matrix is an API boundary because it is the evidence behind "we support Bazel 8 and 9." A deprecation note is an API boundary because it gives users time to move without reading private implementation history.
The common mistake is to treat packaging as a separate operations task: first write the rules, then "add release automation." If public entry points are unclear, CI cannot test the right path. If examples use private helpers, Stardoc and BCR docs teach the wrong contract. If toolchainization leaks build-from-source dependencies to release consumers, module metadata becomes heavier than users expect. If compatibility policy is written after the breakage, the release already taught users that the surface was unstable.
Version means both graph semantics and artifact identity. If the metadata says one thing, the release archive contains another, and the registry entry points at unstable bytes, users experience that mismatch as a dependency failure long before they reach the rule implementation.
Read It By Adoption Stage
Start with 4.11.1 Ruleset Layout & Public Entry Points when turning internal rules into a reusable ruleset. It gives you the vocabulary for public entry points, private internals, examples, docs, and source-vs-release layout. Move to 4.11.2 Toolchainization as soon as users must install compilers, SDKs, runtimes, or helper tools. This is especially important after 4.10 Authoring Module Extensions, because the published setup API is often a module extension plus generated repositories rather than a hand-written list of external deps.
Read 4.11.3 Module Metadata & Version Semantics before choosing module names, dependency minimums, or Bazel-version signals. Read 4.11.4 Publishing Rulesets for the tag-to-BCR handoff: archives, hashes, patches, overlays, docs artifacts, and release snippets. Finish with 4.11.5 Ruleset CI/CD Patterns and 4.11.6 Rule Compatibility & Bazel Version Policy before broad adoption. If the ruleset lacks analysis tests, downstream fixtures, examples, or generated docs, loop back through 4.5 Rule Testing & Documentation before treating publishing as release automation.
Read CI and policy as a pair after release mechanics. Without CI, compatibility is a hope. Without policy, a green matrix does not tell users what they may rely on.
Decide: A ruleset release builds in its own repository, but its setup example loads a private .bzl path, compatibility promises exist only in maintainer memory, and publication still requires undocumented manual steps. Is the release contract ready, and what evidence is missing?
Reveal
It is not ready for downstream adoption. Users need tested public entry points and consumer-shaped examples, registry metadata and release artifacts with stable versions and hashes, a stated compatibility policy backed by the supported CI matrix, and a repeatable release path.
A successful internal build proves implementation, not upgradeability. A user should be able to install from the registry, follow only public documentation, and understand what changes the project promises to support.
Use the mini-ruleset as a compact release candidate: its BCR metadata templates and release-plan script expose the registry and automation surfaces together.
Ruleset publishing turns implementation into a dependency contract. Layout tells users what is public, toolchainization hides setup complexity, module metadata shapes the dependency graph, release artifacts provide verifiable bytes, CI proves supported combinations, and compatibility policy gives users a manageable upgrade path.
Sections in this chapter · 6
Organizing public .bzl APIs, private internals, examples, docs, and module metadata.
Packaging SDK download and registration as generated toolchain repos, usually via module extensions.
Designing MODULE.bazel metadata, compatibility_level, and cross-module version constraints.
Release artifacts, BCR publishing, integrity hashes, and starter templates for community rulesets.
Reusable CI workflows, cross-version testing, documentation checks, and release gates for rulesets.
Defining supported Bazel versions, stable rule APIs, deprecations, and upgrade signals for downstream users.