adminops / provider mirror

Using the mirror

Three ways in, depending on whether you are on a workstation, inside CI, or building an air-gapped bundle. All of them are plain HTTPS GET against https://mirror.fadminops.space/v1/providers/.

1. Workstation

The CLI configuration file lives at ~/.terraformrc on Linux and macOS, %APPDATA%\terraform.rc on Windows. A network_mirror block replaces the default installation method for the prefixes you list.

provider_installation {
  network_mirror {
    url = "https://mirror.fadminops.space/v1/providers/"
    include = ["registry.terraform.io/hashicorp/*"]
  }
  direct {
    exclude = ["registry.terraform.io/hashicorp/*"]
  }
}

Confirm it took effect — the log line names the source for every provider it resolves:

$ TF_LOG=trace terraform init 2>&1 | grep -i "provider mirror"
... using provider mirror at https://mirror.fadminops.space/v1/providers/

Lock files are portable. .terraform.lock.hcl records upstream checksums, not mirror URLs, so a lock file produced against the mirror verifies against registry.terraform.io and the other way round. Do not regenerate locks just because you switched.

2. CI

Ship the same block as a file and point the CLI at it with TF_CLI_CONFIG_FILE. Nothing else in the pipeline changes.

# .gitlab-ci.yml / any runner
before_script:
  - printf '%s\n' \
      'provider_installation {' \
      '  network_mirror { url = "https://mirror.fadminops.space/v1/providers/" }' \
      '}' > /tmp/tf.rc
  - export TF_CLI_CONFIG_FILE=/tmp/tf.rc

Runners on our own estate resolve the mirror over the internal network; from anywhere else it is the same public endpoint, so a pipeline behaves identically on a laptop and on a runner.

3. Air-gapped bundle

For environments with no egress at all, mirror to a directory and copy the directory in. This produces the filesystem layout terraform expects under filesystem_mirror:

$ terraform providers mirror -platform=linux_amd64 ./providers
$ tar czf providers.tgz ./providers
# on the target host: extract, then
provider_installation {
  filesystem_mirror { path = "/opt/terraform/providers" }
}

Protocol

The mirror implements the provider network mirror protocol. Two document types, both JSON, both cacheable. There is no index of the whole namespace by design — you ask for a provider you already know the address of.

RequestReturns
GET /v1/providers/<host>/<ns>/<type>/index.jsonAvailable versions
GET /v1/providers/<host>/<ns>/<type>/<version>.jsonArchives per platform, with hashes

Worked example:

$ curl -s https://mirror.fadminops.space/v1/providers/registry.terraform.io/hashicorp/random/index.json
{
  "versions": {
    "3.7.2": {},
    "3.7.1": {},
    "3.6.3": {}
  }
}

and one level down, the archives for a single version:

$ curl -s .../hashicorp/random/3.7.2.json
{
  "archives": {
    "linux_amd64": {
      "url": "terraform-provider-random_3.7.2_linux_amd64.zip",
      "hashes": ["h1:cFGCdxTlsrTeVhIcTHJ1nzdT1zpBGwOO9uL9dgvMYRc="]
    }
  }
}

Archive URLs are relative to the document that names them, so the mirror can be re-hosted behind another path or another name without rewriting any JSON.

Verifying what you got

The h1: hashes are upstream values copied verbatim; the mirror never recomputes or re-signs anything. To check a download by hand:

$ terraform providers lock -platform=linux_amd64
$ grep -A3 'hashicorp/random' .terraform.lock.hcl

If a version is missing. The mirror only serves what the last sync copied. A brand-new release can be up to six hours behind upstream — see Status for the current window. Pin to a version that exists, or fall back to direct for that one prefix.