Domain 3 of 8 · Chapter 2 of 7

Initialize a working directory

What terraform init sets up

The first thing you do with any new or freshly cloned Terraform configuration is run terraform init[1]: until it finishes, terraform plan and terraform apply have no backend, no modules, and no provider plugins to work with. init runs three setup steps in a fixed order and records what it installed, and this section covers what those steps install, where the files land, and why re-running init is always safe.

The three setup steps

init prepares the working directory by running these steps in order[1]:

  1. Initialize the backend. init reads the backend configuration from the root module and initializes the chosen backend, the place your state is stored.
  2. Install child modules. init searches the configuration for module blocks and retrieves each module from the location in its source argument.
  3. Install provider plugins. Providers ship separately from Terraform as plugins, so init finds every provider the configuration references and downloads its plugin.

Everything lands in a local .terraform directory that init creates to hold the installed plugins, modules, and backend settings. You can relocate that directory with the TF_DATA_DIR environment variable.

Safe to run again

init is safe to run multiple times[1]. Re-running it brings the directory up to date with configuration changes, installing newly added modules and providers, and it never deletes your existing configuration or state. That is why the fix for most not-installed provider or module errors is simply to run init again. init only prepares the directory: it never provisions, changes, or destroys infrastructure, so a stray init cannot harm running resources.

terraform init 1. Initialize the backend 2. Install child modules 3. Install provider plugins Writes the .terraform directory and the .terraform.lock.hcl lock file
The three setup steps terraform init runs in order, then the .terraform directory and dependency lock file it writes.

Providers, the lock file, and -upgrade

A plain terraform init[1] does not chase the newest provider releases, and that is deliberate: reproducible runs depend on every init selecting the same versions. Terraform reaches that goal with the dependency lock file.

The dependency lock file

After it installs providers, init records the exact provider versions it selected, along with their checksums[2], in a file named .terraform.lock.hcl in the working directory. Commit this file to version control[2] so teammates and CI select exactly the same provider versions on their next init, and so version changes show up in code review. When the file is present, a later init re-selects the recorded version even if a newer one now exists.

Forcing an upgrade

To move off the locked versions, run terraform init -upgrade[1]. It ignores the selections in the lock file, installs the newest version of each provider that the configuration's version constraints still allow, and rewrites the lock file with the new versions and checksums. Run it deliberately rather than on every init, so an upgrade is a reviewed change instead of an accident.

Where plugins come from

By default init downloads each provider into the working directory's .terraform directory, so every configuration keeps its own copy. Two settings change that source:

  • terraform init -plugin-dir=PATH forces init to read plugins only from the given directory, as if it were a filesystem_mirror[1] in the CLI configuration. It bypasses the registry and every other install method, which is how you install providers in an air-gapped environment.
  • Setting the TF_PLUGIN_CACHE_DIR[3] environment variable, or plugin_cache_dir in the CLI config, gives Terraform a shared plugin cache so each plugin binary is downloaded once and reused across working directories.
terraform init installs a provider -upgrade given? yes no ignore lock, select newest allowed, rewrite lock file lock file has a version? yes no re-select the locked version select newest allowed, write the lock file
How terraform init chooses a provider version: honor the lock file unless -upgrade re-selects the newest allowed version.

Re-initializing the backend: migrate, reconfigure, skip

Change the backend block and your next command stops with a backend-changed error until you re-run terraform init[1]. Re-running init is the only way to apply a backend change; no terraform state subcommand does it. What happens to your existing state then comes down to one flag.

Migrate, reconfigure, or skip

  • terraform init -migrate-state[1] attempts to copy your existing state into the newly configured backend, prompting for confirmation when workspace states are involved. Use it when you are moving real state from one backend to another, for example from local to S3.
  • terraform init -reconfigure[1] disregards the existing backend configuration and initializes from scratch, which explicitly prevents migration: no state is copied. Use it to point the directory at a fresh, empty backend.
  • terraform init -backend=false[1] skips backend initialization entirely. It is intended for a directory that was already initialized for a backend, letting you reinstall plugins and modules, for example to run terraform validate in CI, without touching remote state.

The distinction that trips people up is migrate versus reconfigure: -migrate-state copies the state into the new backend, while -reconfigure throws away the old backend configuration and copies nothing. Reach for -reconfigure only when you do not want the old state carried over.

Flag Backend initialized Existing state
plain init Yes Prompts to migrate if the backend changed
-migrate-state Yes Copied into the new backend
-reconfigure Yes Not copied; old config discarded
-backend=false No Untouched; backend step skipped

Partial backend configuration

You do not have to hard-code every backend setting. terraform init -backend-config=...[1] supplies backend settings at init time, from a file or as key=value pairs, which keeps dynamic or sensitive values such as credentials out of the committed configuration.

Configuration loading and exam-pattern recognition

Before init can install anything, it has to know what the configuration declares, and that means loading every file in the directory. Terraform treats the whole module as a single document[4]: it reads and merges all .tf and .tf.json files in the working directory, and because the language is declarative, the order it reads them in has no effect on behavior. Splitting code across main.tf, variables.tf, and outputs.tf is purely a convention for readers[4]; those names are not required, and Terraform does not run files top to bottom.

Two boundaries qualify that flat merge:

  • Nested directories are separate modules. A module is only the top-level files in a directory; subdirectories are not automatically included[4] and load only when a module block calls them.
  • Override files are applied last. Any file named override.tf, override.tf.json, or ending in _override.tf or _override.tf.json is skipped on the first pass and processed last[5], merging its blocks over the matching already-defined blocks. Use them sparingly, because they hide changes from the reader.

Exam-pattern recognition

  • A stem shows a run that failed because a provider or module is not installed, or a freshly cloned repository that has never been initialized. The answer is terraform init, not plan, apply, or refresh: init is the only command that installs dependencies.
  • A stem wants newer provider versions, but a plain init keeps installing the old ones. The lock file is pinning them, so the answer is terraform init -upgrade.
  • A stem changes the backend and asks how to keep the existing state. The answer is to re-run terraform init -migrate-state; distractors offer terraform state mv (which moves individual resources, not backends) or -reconfigure (which discards the old backend without copying state).
  • A stem claims filenames like main.tf are required, or that files run in order. Both are false: Terraform merges all .tf files regardless of name or order, and override files are the only ones applied last.

How each init invocation treats the backend and state

Behaviorplain init-migrate-state-reconfigure-backend=false
Initializes the backendYesYesYesNo
Copies existing state to the new backendPrompts if the backend changedYes, copies itNo, discards old configNot applicable
Installs providers and modulesYesYesYesYes
Typical triggerFirst init or new dependencyMoving state to a new backendRe-init backend without migratingInstall plugins to validate only

Decision tree

Installing plugins only?yes-backend=falsenoBackend block changed?yesnoKeep existing state?Upgrade provider versions?yesno-migrate-state-reconfigureyesno-upgradeterraform init

Sharp facts the exam loves — give these one last read before exam day.

Cheat sheet

Sharp facts the exam loves — scan these before test day.

init performs backend, module, and provider setup

terraform init initializes a working directory by initializing the backend, downloading and installing child modules referenced in module blocks, and installing the provider plugins the configuration requires.

Trap Thinking init applies changes or refreshes state; init only prepares the working directory and never provisions infrastructure.

10 questions test this
init is the first command and creates .terraform

terraform init is the first command run against a new or cloned configuration; it creates the local .terraform directory used to store backend configuration, provider plugins, and modules.

5 questions test this
init is safe to run multiple times

terraform init is idempotent and safe to run repeatedly; it installs newly added modules and providers but never deletes existing configuration or state.

-upgrade ignores locked provider selections

terraform init -upgrade upgrades all previously selected providers to the newest versions allowed by the configuration's version constraints, ignoring the versions recorded in the dependency lock file.

Trap Believing a plain terraform init upgrades providers; without -upgrade it honors the versions already recorded in .terraform.lock.hcl.

8 questions test this
init writes the dependency lock file

During provider installation terraform init records the selected provider versions and checksums in .terraform.lock.hcl, which should be committed to version control so future inits select the same versions.

12 questions test this
-plugin-dir forces a local plugin source

terraform init -plugin-dir=PATH forces Terraform to read provider plugins only from the specified directory, bypassing the registry and other install methods.

init -migrate-state moves state to a new backend

After changing the backend configuration, you re-run terraform init; the -migrate-state option copies the existing state file into the newly configured backend.

Trap Reaching for terraform state mv, push, or refresh to move to a new backend; backend migration is done by re-running terraform init.

7 questions test this
-reconfigure skips state migration

terraform init -reconfigure disregards any existing backend configuration and reinitializes from scratch, explicitly preventing migration of existing state to the new backend.

Trap Confusing -reconfigure with -migrate-state; -reconfigure discards the old backend without copying state, while -migrate-state copies it.

8 questions test this
-backend=false initializes without a backend

terraform init -backend=false skips backend initialization, which is useful for installing plugins and modules to validate a configuration without accessing remote state.

Terraform merges all .tf/.tf.json files order-independently

Terraform loads and merges every .tf and .tf.json file in the working (top-level) directory into a single configuration, evaluating the whole module as one document; nested directories are treated as separate modules and are not auto-loaded. Because the language is declarative, the order in which files are loaded does not matter, so splitting code across main.tf, variables.tf, and outputs.tf is purely an organizational convention with no effect on behavior.

Trap Assuming filenames like main.tf/variables.tf/outputs.tf are required or that Terraform executes files in a top-to-bottom or filename order — it does not. The lone exception to flat merging is override files (override.tf, override.tf.json, or any *_override.tf / *_override.tf.json), which are loaded last and merged in to override matching blocks.

6 questions test this

Also tested in

References

  1. Command: terraform init
  2. The Dependency Lock File (.terraform.lock.hcl)
  3. CLI Configuration File
  4. Configuration Files and Directories
  5. Override Files