Component · Init

terraform init

Prepares the working directory by installing modules and provider plugins, then writing a dependency lock file. No cloud APIs are called for infrastructure.

Init Flow Diagram
InitCommand.Run()  [command/init.go:50]
  │
  ├─► Parse flags & arguments
  ├─► c.initConfigLoader()        — create configload.Loader
  ├─► loadConfig(path)            — parse *.tf files in dir
  │
  ├─── MODULES ───────────────────────────────────────────────────
  │    installModules(ctx, path, testsDir, upgrade)
  │      └─► ModuleInstaller.InstallModules()  [initwd/module_install.go:66]
  │            │
  │            ├── Walk root *configs.Module.ModuleCalls
  │            │     Each ModuleCall has a Source (registry, git, local)
  │            │     and a VersionConstraint
  │            │
  │            ├── For registry sources:
  │            │     registry.Client.ModuleVersions()  →  available versions
  │            │     Select highest version matching constraint
  │            │     registry.Client.ModuleLocation()  →  download URL
  │            │
  │            ├── Download & unpack to .terraform/modules//
  │            │
  │            ├── Recurse into child modules (BFS)
  │            │
  │            └── Write .terraform/modules/modules.json
  │                  { "Modules": [ { "Key": ".", "Source": "...", "Dir": "..." } ] }
  │
  ├─── PROVIDERS ─────────────────────────────────────────────────
  │    getProviders(ctx, config, state, upgrade)
  │      │
  │      ├─► Build requirements map from *configs.Config
  │      │     RequiredProvider blocks  →  map[addrs.Provider]version.Constraints
  │      │
  │      ├─► getproviders.Source.AvailableVersions(provider)
  │      │     Default: Terraform Registry  registry.terraform.io
  │      │     Alternatives: local mirror, network mirror
  │      │
  │      ├─► Version selection (highest matching all constraints)
  │      │
  │      ├─► getproviders.Source.PackageMeta(provider, version, platform)
  │      │     →  download URL + expected SHA256
  │      │
  │      ├─► HTTP download  →  .terraform/providers/registry.terraform.io/…/
  │      │
  │      └─► Verify SHA256 checksum
  │
  └─── LOCK FILE ─────────────────────────────────────────────────
       depsfile.Locks.SetProviderLocked(provider, version, hashes)
         └─► Write .terraform.lock.hcl
               provider "registry.terraform.io/hashicorp/aws" {
                 version     = "5.0.0"
                 constraints = ">= 4.0, < 6.0"
                 hashes      = [ "h1:...", "zh:..." ]
               }
InitCommand entry point

The InitCommand struct lives in internal/command/init.go. Its Run method is the first thing called after the CLI routes terraform init.

internal/command/init.go:44–111 GitHub
type InitCommand struct {
    Meta
    // ...
}

func (c *InitCommand) Run(args []string) int {
    // Parse flags (upgrade, backend, reconfigure, etc.)
    // ...

    // Load configuration from the target directory
    path, err := modulePath(args)
    // ...

    // Install modules from source addresses in config
    installAbort, installDiags := c.installModules(ctx, path, testsDir,
        upgrade, false, hooks)
    diags = diags.Append(installDiags)
    if installAbort || diags.HasErrors() {
        return 1
    }
    // ...
}
ModuleInstaller — module resolution

ModuleInstaller in internal/initwd/module_install.go is responsible for walking the module call graph and downloading each external module into the local .terraform/modules/ cache.

internal/initwd/module_install.go:35–64 GitHub
type ModuleInstaller struct {
    modsDir     string             // .terraform/modules/
    loader      *configload.Loader
    reg         *registry.Client
    // caches of registry responses
    registryPackageVersions map[addrs.ModuleRegistryPackage]*response.ModuleVersions
    registryPackageSources  map[addrs.ModuleRegistryPackage]map[string]*response.ModuleSource
    // ...
}

// InstallModules walks the module call graph breadth-first,
// downloading each remote source into modsDir.
func (i *ModuleInstaller) InstallModules(
    ctx context.Context,
    rootDir string,
    testsDir string,
    upgrade bool,
    installErrsOnly bool,
    hooks ModuleInstallHooks,
) (*configs.Config, tfdiags.Diagnostics)

Module source types

  • Registry: registry.terraform.io/namespace/module/provider — fetched via the Terraform Registry API
  • Git: git::https://… — cloned with go-getter
  • HTTP/S3/GCS: archive downloaded and extracted via go-getter
  • Local: relative path like ./modules/vpc — symlinked or copied

Output: modules.json

After installation, the module manifest is written to .terraform/modules/modules.json. The config loader reads this at runtime to locate each module's source directory.

.terraform/modules/modules.json (example)
{
  "Modules": [
    {
      "Key":    "",
      "Source": "",
      "Dir":    "."
    },
    {
      "Key":    "vpc",
      "Source": "registry.terraform.io/terraform-aws-modules/vpc/aws",
      "Version": "5.1.0",
      "Dir":    ".terraform/modules/vpc"
    }
  ]
}
Provider discovery & download

Provider binaries are discovered via the getproviders.Source interface and cached in .terraform/providers/. The default source is the Terraform Registry at registry.terraform.io.

internal/getproviders/package_authentication.go + source interfaces GitHub
// Source is the interface for provider package discovery.
// Implementations: RegistrySource, FilesystemMirrorSource, MultiSource.
type Source interface {
    AvailableVersions(ctx context.Context, provider addrs.Provider) (
        VersionList, Warnings, error)

    PackageMeta(ctx context.Context, provider addrs.Provider,
        version Version, target Platform) (PackageMeta, error)
}

// PackageMeta describes a single provider binary package.
type PackageMeta struct {
    Provider         addrs.Provider
    Version          Version
    TargetPlatform   Platform         // e.g. "linux_amd64"
    Filename         string
    Location         PackageLocation  // URL or local path
    Authentication   PackageAuthentication
}

Provider cache layout

.terraform/providers/ (directory structure)
.terraform/providers/
└── registry.terraform.io/
    └── hashicorp/
        └── aws/
            └── 5.0.0/
                └── darwin_arm64/
                    └── terraform-provider-aws_v5.0.0_x5
Dependency lock file

After resolving and downloading providers, init writes .terraform.lock.hcl using depsfile.Locks. This file should be committed to version control to ensure reproducible installs.

internal/depsfile/locks.go GitHub
// Locks records the locked provider selections.
type Locks struct {
    providers   map[addrs.Provider]*ProviderLock
    // ...
}

type ProviderLock struct {
    addr        addrs.Provider
    version     getproviders.Version
    constraints getproviders.VersionConstraints
    hashes      []getproviders.Hash
}
.terraform.lock.hcl (example)
provider "registry.terraform.io/hashicorp/aws" {
  version     = "5.0.0"
  constraints = ">= 4.0.0, < 6.0.0"
  hashes = [
    "h1:AbCdEf...",
    "zh:1234567890abcdef...",
  ]
}

Two hash formats: h1: is a hash of the zip archive contents (used for the current platform). zh: is a hash of a list of hashes for all platforms — used to verify cross-platform consistency in CI environments.