hashicorp/terraform · error

failed to create local modules directory: %s

Error message

failed to create local modules directory: %s

What it means

Raised at the start of module installation when os.MkdirAll fails to create the local modules directory (the .terraform/modules path used to cache downloaded module sources). The wrapped %s is the OS-level mkdir error, typically a permission denial, a read-only filesystem, an invalid path, or insufficient space/inodes.

Source

Thrown at internal/command/meta_config.go:292

	return body, diags
}

// installModules reads a root module from the given directory and attempts
// recursively to install all of its descendant modules.
//
// The given hooks object will be notified of installation progress, which
// can then be relayed to the end-user. The uiModuleInstallHooks type in
// this package has a reasonable implementation for displaying notifications
// via a provided cli.Ui.
func (m *Meta) installModules(ctx context.Context, rootDir, testsDir string, upgrade, installErrsOnly bool, hooks ...initwd.ModuleInstallHook) (abort bool, diags tfdiags.Diagnostics) {
	ctx, span := tracer.Start(ctx, "install modules")
	defer span.End()

	rootDir = m.normalizePath(rootDir)

	err := os.MkdirAll(m.modulesDir(), os.ModePerm)
	if err != nil {
		diags = diags.Append(fmt.Errorf("failed to create local modules directory: %s", err))
		return true, diags
	}

	loader, err := m.initConfigLoader()
	if err != nil {
		diags = diags.Append(err)
		return true, diags
	}

	initializer := func(rootMod *configs.Module, walker configs.ModuleWalker) (*configs.Config, tfdiags.Diagnostics) {
		variables, diags := backendrun.ParseConstVariableValues(m.VariableValues, rootMod.Variables)
		ctx, ctxDiags := terraform.NewContext(&terraform.ContextOpts{
			Parallelism: 1,
		})
		diags = diags.Append(ctxDiags)
		if diags.HasErrors() {
			return nil, diags
		}

View on GitHub (pinned to c9def3e214)

Solutions

  1. Check the wrapped error: if it is a permission error, grant write access to the working directory (or TF_DATA_DIR).
  2. If TF_DATA_DIR is set, ensure the path exists and is writable, or unset it to use the default .terraform.
  3. Run init in a writable working directory.
  4. Free disk space / inodes if the filesystem is full.

Example fix

// before
// terraform init  -> 'failed to create local modules directory: permission denied'

// after
chmod -R u+w .terraform
# or set a writable data dir:
export TF_DATA_DIR=/tmp/tfdata && terraform init
Defensive patterns

Strategy: validation

Validate before calling

// Verify the modules directory is writable before running init
dir := ".terraform/modules"
if v := os.Getenv("TF_DATA_DIR"); v != "" {
    dir = filepath.Join(v, "modules")
}
if err := os.MkdirAll(dir, 0o755); err != nil {
    log.Fatalf("cannot write modules dir: %v", err)
}

Prevention

When it happens

Trigger: installModules calls os.MkdirAll(m.modulesDir(), os.ModePerm) and err!=nil. m.modulesDir() resolves to .terraform/modules under the working directory (or TF_DATA_DIR if set). Triggered by: read-only mount, no write permission on cwd, an invalid TF_DATA_DIR, a path whose parent doesn't exist, or a full disk.

Common situations: Running `terraform init` in a directory the process cannot write to (e.g. system path, mounted RO image); TF_DATA_DIR pointing to a non-writable or non-existent location; container running as non-root user without volume write perms; filesystem full.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/bbfc7f126799d650. Report an issue: GitHub.