Terraform provider network mirror
OrbitalReg's terraform format hosts two distinct Terraform wire protocols under the same repo. This page covers the provider network mirror protocol (HashiCorp reference). For the module registry protocol (terraform init resolving a source = "myorg/mymodule/aws" reference), see the format reference.
It does not use service discovery
The module registry protocol and the provider mirror protocol are easy to conflate — both serve Terraform CLI requests under the same terraform repo — but they differ on one important point: the mirror protocol never touches .well-known/terraform.json. The HashiCorp docs state this explicitly: a network mirror location "does not use the service discovery indirection, because a network mirror location is only a physical location." .well-known/terraform.json (terraform.go's handleDiscovery) exists solely for the module registry protocol; it plays no role here.
Instead, the mirror base URL is configured directly in the CLI's provider_installation block:
provider_installation {
network_mirror {
url = "https://orbitalreg.example.com/terraform/<project>/<repo>/providers/"
}
}Terraform never requests the base URL itself — only the two JSON operations below, relative to it.
Wire operations
| Operation | Path | Notes |
|---|---|---|
| List versions | GET {mirror}/{hostname}/{namespace}/{type}/index.json | {"versions": {"2.0.0": {}, ...}}; 404 if the provider is unknown. |
| List archives | GET {mirror}/{hostname}/{namespace}/{type}/{version}.json | {"archives": {"<os>_<arch>": {"url": "...", "hashes": [...]}}}; url resolves relative to this response. |
| Download | the url from the archives response | Redirects to the stored .zip, same presigned-S3 pattern as every other format. |
hashes is populated as zh:<sha256-hex> — OrbitalReg hashes the raw zip body it stores, not a dirhash of the extracted contents, so only the zh: scheme is emitted (h1: dirhashes aren't produced). Per the protocol, archive downloads never carry credentials — only the two JSON endpoints do, and only if your CLI config supplies them.
No SSRF exposure, no air-gapped egress channel
:hostname in the path is purely a metadata field OrbitalReg looks up in its own artifacts table (alongside :namespace, :type, and :version) — never a target OrbitalReg itself requests. This is unlike a real upstream-proxying mirror (or Go's sumdb proxy support, where the requested path segment is client-controlled): there is no outbound request to a client-controlled host, so the provider mirror needs no entry in the air-gapped egress allowlist.
Populating the mirror
The wire protocol itself is read-only from the CLI's point of view — mirrors don't accept uploads over the standard protocol. OrbitalReg adds its own upload endpoint to populate one:
PUT /terraform/<project>/<repo>/providers/{hostname}/{namespace}/{type}/{version}/{os}/{arch}/{filename}Requires developer role or above, same gate as module tarball uploads. Typical use: mirror registry.terraform.io providers into your own OrbitalReg instance for air-gapped terraform init runs, or publish an internal provider that was never meant to reach the public registry.
Repo browser
Provider archives land in the same artifacts table as module tarballs (metadata->>'kind' = 'terraform.provider' vs. the module path's absence of that field) and are stored through the same formatutil.Upsert path. They show up in the existing generic artifact table on a terraform repo's detail page — there's no separate provider-specific browser view, and none is needed.