DEV Community

sys-ronin
sys-ronin

Posted on

Terraform Offline Provider Setup: Zero Downloads & Clean Repos with Local Mirrors

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 init across 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 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"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Critical details:

  • File must be named exactly .terraformrc (not terraform.rc, not .terraformrc.txt)
  • Must reside in $HOME/.terraformrc (Linux/macOS) or %USERPROFILE%\terraform.rc (Windows)
  • The include pattern uses the full source address: registry.terraform.io/oracle/oci
  • Do not add /* at the end — oracle/oci is 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" and version = "6.20.0"
  • It checks .terraformrc and finds a filesystem_mirror matching 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 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
Enter fullscreen mode Exit fullscreen mode

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"
    ]
  }
}
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 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
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.

Top comments (0)