Skip to content

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:

hcl
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 ​

OperationPathNotes
List versionsGET {mirror}/{hostname}/{namespace}/{type}/index.json{"versions": {"2.0.0": {}, ...}}; 404 if the provider is unknown.
List archivesGET {mirror}/{hostname}/{namespace}/{type}/{version}.json{"archives": {"<os>_<arch>": {"url": "...", "hashes": [...]}}}; url resolves relative to this response.
Downloadthe url from the archives responseRedirects 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.

See also ​

OrbitalReg ships under the OrbitalReg EULA with a Free Forever tier.