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
Enter fullscreen mode Exit fullscreen mode
Important Naming Rules
Element Rule Directory path Must followregistry.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 = ["oracle/oci"]
}
# Everything else falls back to internet
direct {
exclude = ["oracle/oci"]
}
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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)
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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 Mirrorterraform 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
Enter fullscreen mode Exit fullscreen mode
Update .terraformrc:
provider_installation {
filesystem_mirror {
path = "/home/unknown/.terraform.d/plugins-local"
include = [
"oracle/oci",
"hashicorp/random"
]
}
direct {
exclude = [
"oracle/oci",
"hashicorp/random"
]
}
}
Enter fullscreen mode Exit fullscreen mode
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 this format 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
Enter fullscreen mode Exit fullscreen mode
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.
답글 남기기