4.9.2 Archive Downloads & Integrity
Bazel's safest external-dependency path is a small contract: download this exact archive, verify these exact bytes, extract them into an external repository, and expose them through generated or overlaid BUILD files. That contract is what lets repository rules participate in reproducible builds even though they run during loading and can reach the network outside ordinary action sandboxing.1
The Archive Contract
For custom repository rules, the core API is repository_ctx.download() for a file and repository_ctx.download_and_extract() for an archive. Both accept one URL or a list of mirror URLs, can verify either sha256 or Subresource Integrity integrity, and return the computed hashes after a successful download.2 The built-in http_archive, http_file, and http_jar rules are the higher-level versions most users see first: http_archive downloads a compressed archive, decompresses it, and makes its targets available as a repository.3
A minimal custom rule often looks like this shape:
def _sdk_repo_impl(rctx):
rctx.download_and_extract(
url = rctx.attr.urls,
sha256 = rctx.attr.sha256,
strip_prefix = rctx.attr.strip_prefix,
)
rctx.file("BUILD.bazel", rctx.attr.build_file_content)
The SDK name is incidental. The pattern is to expose urls, sha256, and layout decisions such as strip_prefix as attributes of the repository rule rather than hide them in shell script state. A Go toolchain archive is the canonical example: urls for mirrors, sha256 for the expected checksum, download_and_extract() to unpack, and a template-generated BUILD file to make the extracted tree usable by Bazel.4
strip_prefix is not the only extraction control. repository_ctx.download_and_extract() and repository_ctx.extract() also accept strip_components, which removes a fixed number of leading path components and is mutually exclusive with strip_prefix.2 For custom repository rules, rename_files can rename archive entries before prefix stripping, which is useful for problematic filenames or case-insensitive filesystem collisions.2
The Hash Is Both Security And Cache Key
sha256 and integrity are not decorative. The official API docs call it a security risk to omit the checksum because remote files can change. At best, omitting it makes the build non-hermetic.2 The same checksum is also what lets Bazel avoid network access: when a hash is supplied, Bazel first checks the repository cache for that file, downloads only if it is missing, and adds a successful download back to the cache.2
That means a good archive dependency gets two properties at once. It rejects unexpected bytes, and it can be reused across local workspaces and Bazel invocations without asking the upstream server again. A repository rule that shells out to wget inside a genrule gets neither property in the same structured way: Bazel does not know the action is an external fetch, bazel fetch and bazel vendor cannot manage it as a repository dependency, credentials tend to leak into build files, the tool dependency is undeclared, and Bazel's downloader cache is bypassed.5
There is one subtle cache-safety detail for rule authors: when sha256 or integrity is user-specified, the repository_ctx docs recommend setting an explicit canonical_id.2 The built-in HTTP repository rules use URL-derived canonical IDs by default so that changing URLs without changing the hash is harder to miss. Otherwise, a stale local cache entry can hide a bad update until another machine tries to fetch from scratch.3
Prefer Archives To Git Checkouts
git_repository is useful when an archive genuinely is not available, but it is a weaker default. The official Git repository rule docs explicitly prefer http_archive: Git repository rules depend on system git(1), support only a single remote rather than a mirror list, and do not work with the repository cache the way http_archive does.6 Branches and tags are also not the same as immutable bytes. The rule may return reproducible parameters for the commit it checked out, but the fetch still depends on Git behavior and server support.6
Stable archive selection matters even when the URL is HTTPS and the hash is present. The Bzlmod migration guide warns that GitHub-generated source archives under /archive/... are not checksum-stable and recommends uploading release artifacts under /releases/download/... instead.7 BCR-facing infrastructure made the same lesson part of dependency hygiene: 3.1.6 BCR Infrastructure covers registry mirrors, source.json, and why generated tarballs are a fragile source artifact.
portable-ruby is a concrete release-side example: it publishes suffixed,
platform-specific interpreter archives intended for checksum-pinned downstream
use, and attaches provenance before publication.8 Treat its immutability
statement as release policy rather than a substitute for verification—the
workflow can upload with --clobber, so consumers still need to pin and check
the exact archive bytes.
Large binary content has another trap. A git_repository clone does not solve Git LFS by itself. One real-world Open Image Denoise bazelization had to clone pretrained weights without Git LFS because Bazel's git_repository rule did not support the LFS-backed dependency they needed.9 For large binary payloads, prefer a release artifact, object-store artifact, or rule-specific downloader path whose bytes and checksum are explicit.
Mirrors, Auth, And Policy Hooks
Mirror lists belong in the dependency contract. http_archive and repository_ctx.download() accept multiple URLs for the same file, and the HTTP rule docs say the list is tried in order until one succeeds, so local or company mirrors can be placed first.2,3 Registry-backed dependencies use the same archive-with-hash principle, but their source.json and registry mirror mechanics belong in 3.1.6 BCR Infrastructure. At the repository-rule layer, the lesson is to keep custom fetches on Bazel's downloader path instead of inventing a parallel one.10
Authentication should stay in the downloader layer, not in BUILD files or generated shell commands. http_archive supports auth_patterns with .netrc, while repository_ctx.download() accepts auth and headers parameters.2,3 Bazel credential helpers extend that model for dynamic credentials: Bazel can invoke a domain-scoped helper, pass the URL on stdin, and use returned HTTP headers for the request, avoiding hard-coded secrets in repository declarations.5 The detailed protocol and scoping belong in 4.9.9 Credential Helper Protocol.
The same archive-fetching contract also gives infrastructure teams policy hooks. The external dependency FAQ recommends internal mirrors for mirror.bazel.build and GitHub source archives, using --downloader_config or --module_mirrors, or a prepopulated download cache to avoid live Internet access for source archives.11 The custom-headers work in Bazel 7.1 made repository_ctx.download(headers = ...) usable for cases such as Docker registries that require an Accept header or Alpine packages that require Range requests, allowing rules to stay on Bazel's downloader instead of falling back to curl.12 Remote downloader support takes this further by routing downloads through a gRPC proxy or pre-seeded cache, which is why it is treated separately in 4.9.10 Remote Downloader.5
What To Encode In A Repository Rule
The practical rule is simple: make the archive bytes the unit of truth. Put the URL list, checksum, extraction layout, archive type when inference is ambiguous, and any auth/header policy in the rule's declared inputs. Generate BUILD files after extraction, but do not let the generated repository depend on ambient network state, local tools, or mutable branch heads.
For built-in http_archive, review these attributes first:
| Attribute | Why it matters |
|---|---|
urls | Ordered mirrors for the same file. Put controlled mirrors before upstream. |
sha256 or integrity | Verifies bytes and enables repository-cache reuse. |
canonical_id | Narrows cache hits so URL/hash update mistakes are easier to catch. |
strip_prefix / strip_components / add_prefix | Normalizes archive layout before BUILD files see it. Use only one of strip_prefix and strip_components. |
remote_patches / remote_file_urls | Keeps fetched overlays and patches hash-checked too. |
auth_patterns / netrc | Keeps authentication out of BUILD-file command strings. |
An archive dependency should be boring: stable URL or mirrors, explicit checksum, deterministic extraction, and generated BUILD files that describe the result. If you cannot name the bytes with sha256 or integrity, Bazel cannot protect you from upstream mutation or reliably reuse the repository cache.
Use http_archive or repository_ctx.download_and_extract() before reaching for git_repository. Use credential helpers, headers, mirrors, and remote downloader infrastructure before reaching for curl, wget, or hard-coded credentials.
Check your understanding · 4 questions
1.Why should a repository rule set sha256 or integrity for downloaded archives before shipping?
Select one answer
2.Which choices keep archive fetching on Bazel's structured downloader path?
Select all that apply
3.Why is http_archive usually preferred over git_repository for third-party source dependencies?
Select one answer
4.True or false: stable archive dependency design.
Choose True or False for each sentence
/archive/... tarballs are a fragile choice for published dependencies because their bytes may change.canonical_id can narrow cache hits so URL/hash drift is easier to catch.git_repository automatically resolves it into the real blob for Bazel.Footnotes
-
Repository Rules — repository rule definition, external-dependency fetching,
repository_ctx, and the refetch lifecycle. ↩ -
repository_ctx —
download()anddownload_and_extract()parameters, checksum verification, repository-cache lookup,canonical_id, auth, headers, and returned hashes. ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
http repository rules —
http_archive,http_file,http_jar, mirror URLs,sha256/integrity,canonical_id, auth patterns, overlays, patches, and archive layout attributes. ↩1 ↩2 ↩3 ↩4 -
Writing Bazel rules: repository rules —
go_downloadexample usingurls,sha256,download_and_extract(),strip_prefix, and template-generated BUILD files. ↩ -
Fetching private data with Repo Rules and MODULE Extensions - Malte Poll, Modus Create — why
genrulepluswgetis the wrong external-fetching abstraction. Repository cache, credential helper, and remote downloader overview. ↩1 ↩2 ↩3 -
git repository rules —
git_repositorybehavior and official reasons to preferhttp_archive. ↩1 ↩2 -
Bzlmod Migration Guide — stable release archive guidance and warning that GitHub-generated
/archive/...source archives are not checksum-stable. ↩ -
portable-ruby repository map — the upstream
README.mdstates the suffixed-tag immutability policy, while.github/workflows/release.ymlcomputes archive subjects, generates provenance, attests them, and publishes the release. ↩ -
Bazelizing Open Image Denoise -- Part 2: More details about the bazelization — practical Git LFS limitation encountered with
git_repository. ↩ -
Bazel registries —
source.jsonarchive schema,integrity,mirror_urls, and registry-wide mirrors inbazel_registry.json. ↩ -
Frequently asked questions — source-archive Internet insulation with internal mirrors,
--downloader_config,--module_mirrors, and prepopulated download cache. ↩ -
rctx.download custom headers coming to Bazel 7.1 —
repository_ctx.download(headers = ...)motivation for Docker registryAcceptheaders, AlpineRangerequests, and staying on Bazel's downloader. ↩