Terraform Offline Provider Setup Guide
Using .terraformrc + .terraform.d for Zero-Download, Repo-Clean Infrastructure
1. The Problem
By default, Terraform:
- Downloads providers from the internet on every
terraform init - Creates a
.terraform/directory inside every project folder - Bloats repositories with cached plugins and lock files
- Breaks in air-gapped or CI/CD environments without internet access
Result: Your repo is dirty, initialization is slow, and builds are non-deterministic.
2. The Solution
Use Terraform's CLI Configuration File (.terraformrc) to define a filesystem mirror. This tells Terraform to resolve providers from a local directory instead of the public registry.
Benefits:
- Zero internet calls for known providers after initial setup
-
No
.terraform/directories inside project folders (only a tiny lock file) -
Instant
terraform initacross all projects using the same provider version - Deterministic builds — every teammate uses the exact same binary
- Air-gapped / CI-ready — works without internet once configured
3. Directory Structure
~/.terraform.d/
├── checkpoint_cache # Terraform version check cache
├── checkpoint_signature
└── plugins-local/ # <-- Your local provider mirror
└── registry.terraform.io/
└── oracle/
└── oci/
├── index.json
├── 6.20.0/
│ └── linux_amd64/
│ └── terraform-provider-oci_v6.20.0_x5 <-- extracted binary
└── 5.47.0/
└── linux_amd64/
└── terraform-provider-oci_v5.47.0_x5
Important Naming Rules
| Element | Rule |
|---|---|
| Directory path | Must follow registry.terraform.io/<namespace>/<name>/<version>/<arch>/
|
| Binary name | Must be terraform-provider-<name>_v<version>_x5
|
No .zip files |
Extract the binary; .zip files are ignored by filesystem_mirror
|
index.json |
Optional — Terraform can work without it for simple mirrors |
4. Step-by-Step Setup
Step 1: Create ~/.terraformrc
This is the Terraform CLI configuration file. It lives in your home directory.
provider_installation {
# Use local filesystem for oracle/oci provider
filesystem_mirror {
path = "/home/unknown/.terraform.d/plugins-local"
include = ["registry.terraform.io/oracle/oci"]
}
# Everything else falls back to internet
direct {
exclude = ["registry.terraform.io/oracle/oci"]
}
}
Critical details:
- File must be named exactly
.terraformrc(notterraform.rc, not.terraformrc.txt) - Must reside in
$HOME/.terraformrc(Linux/macOS) or%USERPROFILE%\terraform.rc(Windows) - The
includepattern uses the full source address:registry.terraform.io/oracle/oci - Do not add
/*at the end —oracle/ociis the correct pattern
Step 2: Prepare the Mirror Directory
mkdir -p ~/.terraform.d/plugins-local/registry.terraform.io/oracle/oci/6.20.0/linux_amd64
Step 3: Extract the Provider Binary
If you downloaded the provider as a .zip:
cd ~/.terraform.d/plugins-local/registry.terraform.io/oracle/oci/6.20.0/linux_amd64
unzip terraform-provider-oci_6.20.0_linux_amd64.zip
mv terraform-provider-oci_v6.20.0 terraform-provider-oci_v6.20.0_x5
Verify:
ls -la ~/.terraform.d/plugins-local/registry.terraform.io/oracle/oci/6.20.0/linux_amd64/
# Should show: terraform-provider-oci_v6.20.0_x5
Step 4: Configure provider.tf in Your Project
In every Terraform stack that uses this provider:
terraform {
required_version = ">= 1.5.0"
required_providers {
oci = {
source = "oracle/oci"
version = "6.20.0"
}
}
}
provider "oci" {
tenancy_ocid = var.tenancy_ocid
user_ocid = var.user_ocid
fingerprint = var.fingerprint
private_key_path = var.private_key_path
region = var.region
}
What happens:
- Terraform reads
source = "oracle/oci"andversion = "6.20.0" - It checks
.terraformrcand finds afilesystem_mirrormatching that address - It copies the binary from
~/.terraform.d/plugins-local/...into memory - No
.terraform/providers/directory is created in your project folder - State backends still work normally (S3, HTTP, local, etc.)
Step 5: Initialize and Verify
cd ~/oci-infra/shared
rm -rf .terraform/ .terraform.lock.hcl # clean slate
terraform init
Expected output:
Initializing provider plugins...
- Finding oracle/oci versions matching "6.20.0"...
- Installing oracle/oci v6.20.0...
- Installed oracle/oci v6.20.0 (unauthenticated)
The key phrase is (unauthenticated). If you see (signed by a HashiCorp partner...), Terraform downloaded from the internet instead of your mirror.
5. Space Savings Breakdown
Before (Default Behavior)
oci-infra/
├── shared/
│ ├── .terraform/ # ~180 MB
│ │ └── providers/
│ │ └── registry.terraform.io/
│ │ └── oracle/oci/6.20.0/...
│ └── .terraform.lock.hcl
├── vm-amd/
│ ├── .terraform/ # ~180 MB (duplicate!)
│ └── .terraform.lock.hcl
└── vm-arm/
├── .terraform/ # ~180 MB (duplicate!)
└── .terraform.lock.hcl
Total: ~540 MB in project folders
After (Local Mirror)
~/.terraform.d/
└── plugins-local/
└── oracle/oci/6.20.0/... # ~180 MB (stored once)
oci-infra/
├── shared/
│ └── .terraform.lock.hcl # ~2 KB
├── vm-amd/
│ └── .terraform.lock.hcl # ~2 KB
└── vm-arm/
└── .terraform.lock.hcl # ~2 KB
Total: ~180 MB (one copy) + ~6 KB lock files
Savings: ~360 MB eliminated. With 10 projects and 3 provider versions, the savings scale to multiple gigabytes.
6. CI/CD & Team Benefits
| Scenario | Without Mirror | With Mirror |
|---|---|---|
terraform init in CI |
30-60s download | 1-2s copy from cache |
| Air-gapped runner | Fails | Works |
| Reproducibility | Depends on registry uptime | 100% offline |
| Repo size | Bloated with .terraform/
|
Clean, only .tf files |
| Parallel jobs | Each downloads separately | Shared system cache |
7. Adding More Providers
To add another provider (e.g., hashicorp/random):
mkdir -p ~/.terraform.d/plugins-local/registry.terraform.io/hashicorp/random/3.6.0/linux_amd64
cd ~/.terraform.d/plugins-local/registry.terraform.io/hashicorp/random/3.6.0/linux_amd64
# Extract binary and rename:
mv terraform-provider-random_v3.6.0 terraform-provider-random_v3.6.0_x5
Update .terraformrc:
provider_installation {
filesystem_mirror {
path = "/home/unknown/.terraform.d/plugins-local"
include = [
"registry.terraform.io/oracle/oci",
"registry.terraform.io/hashicorp/random"
]
}
direct {
exclude = [
"registry.terraform.io/oracle/oci",
"registry.terraform.io/hashicorp/random"
]
}
}
8. Troubleshooting
"Installed provider (signed by HashiCorp partner...)"
Cause: .terraformrc not found or pattern doesn't match.
Fix: Ensure file is at ~/.terraformrc and include uses full address registry.terraform.io/oracle/oci.
"fork/exec ... permission denied"
Cause: Terraform copied a .zip file instead of the extracted binary.
Fix: Remove .zip from the mirror directory; keep only the extracted binary.
"Provider not found in any of the search locations"
Cause: Wrong directory structure or binary naming.
Fix: Verify path ends in .../6.20.0/linux_amd64/terraform-provider-oci_v6.20.0_x5.
9. Quick Reference Commands
# Verify mirror structure
find ~/.terraform.d/plugins-local -type f
# Test if Terraform reads .terraformrc
TF_LOG=DEBUG terraform init 2>&1 | grep -i "filesystem_mirror\|Explicit provider"
# Force re-read backend and provider config
terraform init -reconfigure
# Clean project cache (safe — providers stay in ~/.terraform.d)
rm -rf .terraform/ .terraform.lock.hcl
Summary
| File | Location | Purpose |
|---|---|---|
.terraformrc |
~/.terraformrc |
Tells Terraform where to find local providers |
| Provider binaries | ~/.terraform.d/plugins-local/... |
The actual plugin executables |
provider.tf |
Inside each project | Declares which provider + version to use |
.terraform.lock.hcl |
Inside each project | Locks the provider version (tiny file) |
Result: Your repositories stay clean, initialization is instant, and you are fully offline-capable.
Top comments (0)