| Tested on | Ubuntu 26.04 LTS (Resolute Raccoon) |
|---|---|
| Package | terraform 1.15.8-1hashicorp/local provider 2.9.0 |
| Applies to | Any host with Terraform installed |
| Lab environment | Single Ubuntu VM with Terraform — Terraform lab environment on Ubuntu |
| Privilege | Normal user |
| Scope | terraform init workflow — backend setup, provider and module installation, .terraform and .terraform.lock.hcl, reinitialization, clone workflow, and common flags (-upgrade, -reconfigure, -migrate-state, -backend=false, -backend-config). Does not cover Terraform installation, provider version strategy, module authoring, remote backend setup, or plan/apply tutorials. |
| Related guides | Terraform lab environment on Ubuntu Terraform providers Provider version constraints and lock file Terraform HCL syntax Terraform Associate certification course |
Every Terraform workflow on a machine starts with one command:
terraform initterraform init initializes a Terraform working directory. It prepares the backend, downloads provider plugins and modules your configuration references, processes dependency lock selections, and writes the local metadata Terraform needs before plan or apply can run. This guide walks through that process on Ubuntu with the hashicorp/local provider — no cloud credentials required.
terraform is installed before you start. Installation steps belong in install Terraform on Ubuntu, not here.
What does terraform init do?
terraform init reads the .tf files in the current directory and performs several setup steps:
- Backend initialization — configures where Terraform stores state (default local backend unless you declare another)
- Provider installation — downloads provider plugins declared in
required_providers - Module installation — discovers local modules and downloads remote module packages
- Dependency lock processing — creates or updates
.terraform.lock.hclwith selected provider versions and checksums - Working-directory preparation — writes plugin and module metadata under
.terraform/
*.tf configuration
↓
terraform init
├── backend
├── providers
├── modules
└── dependency selectionsInit is safe to rerun. Later runs are usually quick when nothing changed; Terraform refreshes only what the configuration now requires.
Run terraform init
This section walks the full init cycle: create configuration, run init, inspect what appeared, rerun init, and verify the directory is ready.
Lab configuration
Use an isolated directory under your lab tree:
mkdir -p ~/terraform-labs/terraform-initMove into it — every command below assumes you are here:
cd ~/terraform-labs/terraform-initAdd a minimal resource in main.tf:
resource "local_file" "demo" {
content = "init lab"
filename = "${path.module}/demo.txt"
}Declare the provider requirement in versions.tf:
terraform {
required_version = ">= 1.12.0"
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}The source address tells Terraform which registry plugin to download. Constraint and lock-file depth live in provider version constraints and lock file; this page focuses on what init does with those settings.
First init
Initialize the working directory:
terraform initSample output:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!Read the phases in order:
- Initializing the backend — Terraform sets up the configured backend (implicit local backend here).
- Initializing provider plugins — Terraform resolves the constraint, downloads
hashicorp/localv2.9.0, and installs it under.terraform/providers/. - Lock file message — Terraform records the selected provider version in
.terraform.lock.hcl. - Successfully initialized — the directory is ready for validation and planning.
Files and directories created
List the working directory after the first init:
ls -laSample output:
drwxr-xr-x 3 user user 4096 Aug 11 21:08 .terraform
-rw-r--r-- 1 user user 1257 Aug 11 21:08 .terraform.lock.hcl
-rw-r--r-- 1 user user 96 Aug 11 21:07 main.tf
-rw-r--r-- 1 user user 153 Aug 11 21:07 versions.tfInspect the .terraform tree:
find .terraform -maxdepth 3 -type dSample output:
.terraform
.terraform/providers
.terraform/providers/registry.terraform.io
.terraform/providers/registry.terraform.io/hashicorp| Path | Created by init | Purpose |
|---|---|---|
.terraform/ |
Yes | Local metadata — downloaded providers, module records, backend settings |
.terraform.lock.hcl |
Yes | Provider version selections and package checksums |
terraform.tfstate |
No (this lab) | State appears after terraform apply changes infrastructure |
Second init
Run init again without changing any files:
terraform initSample output:
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/local v2.9.0
Terraform has been successfully initialized!Terraform skips redundant downloads when the lock file and installed plugins already match the configuration.
Verification
Confirm which providers init installed:
terraform providersSample output:
Providers required by configuration:
.
└── provider[registry.terraform.io/hashicorp/local] ~> 2.5Check that the configuration is internally consistent:
terraform validateSample output:
Success! The configuration is valid.Together, terraform providers and terraform validate confirm init prepared dependencies and the .tf files parse correctly. You do not need terraform apply on this page to prove initialization succeeded.
create configuration
↓
terraform init
↓
inspect .terraform / lock file
↓
rerun init
↓
terraform providers
terraform validateWhen should you run terraform init again?
| Situation | Why init is needed |
|---|---|
| New working directory | No .terraform/ metadata exists yet on this machine |
| Cloned repository | .terraform/ is not in version control; plugins must be downloaded locally |
| Provider requirement changes | New or updated required_providers entries need plugin resolution |
| Module source or version changes | Terraform must fetch or refresh module content |
| Backend configuration changes | Backend metadata must be reconfigured or migrated |
| Deliberate provider upgrade | Pass -upgrade to refresh selections within constraints |
Teams commit .tf files and .terraform.lock.hcl, not .terraform/. After you clone a repository, initialize before any other Terraform command:
git clone https://github.com/example/infrastructure.gitEnter the cloned repository:
cd infrastructureDownload providers and prepare the working directory on this machine:
terraform initEach machine downloads its own provider plugins into .terraform/. The lock file keeps provider version selections consistent across laptops and CI runners.
If terraform plan reports that backend initialization is required, init was skipped or the backend block changed since the last successful init.
terraform init options
Common flags that change how init behaves:
| Flag | Purpose |
|---|---|
-upgrade |
Reconsider provider versions within configured constraints and refresh lock-file selections |
-reconfigure |
Reinitialize the backend; ignore prior backend metadata without migrating state |
-migrate-state |
Copy or move existing state when backend configuration changed |
-backend=false |
Skip backend configuration during this init |
-backend-config=FILE |
Merge extra backend settings from a file or key=value pairs |
-get=false |
Skip downloading or updating child modules during init |
The subsections below demonstrate the flags you are most likely to use in daily work.
-upgrade
Pass -upgrade when you want Terraform to reconsider provider versions within your configured constraints:
terraform init -upgradeSample output when the locked version is already the newest match:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Using previously-installed hashicorp/local v2.9.0
Terraform has been successfully initialized!Use -upgrade deliberately after you widen a constraint or when you intend to adopt a newer provider release. Review .terraform.lock.hcl diffs and run terraform plan before apply. Constraint operators and lock-file policy live in provider version constraints and lock file.
Reconfigure or migrate a changed backend
Use a separate lab directory so backend experiments do not overwrite the main init exercise:
mkdir -p ~/terraform-labs/terraform-init-backendSwitch into that backend lab directory:
cd ~/terraform-labs/terraform-init-backendAdd main.tf with the same local_file resource as earlier. Start with a local backend that writes state to one file — add this to versions.tf:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
backend "local" {
path = "state-old.tfstate"
}
}Initialize and create state with apply:
terraform initterraform init exits successfully when the backend and providers are configured. Create managed resources so state exists in state-old.tfstate:
terraform apply -auto-approveChange the backend path in versions.tf:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
backend "local" {
path = "state-new.tfstate"
}
}HashiCorp treats -reconfigure and -migrate-state as alternative responses to a backend configuration change — not steps you run one after the other.
- If you run
-reconfigurefirst, Terraform already accepts the new backend without migrating state - A follow-up
-migrate-stateno longer demonstrates migration from the original backend
| Flag | Behavior |
|---|---|
terraform init -reconfigure |
Accept the new backend settings and ignore prior backend metadata. Existing state is not copied — state-old.tfstate keeps the old records while Terraform points at state-new.tfstate. |
terraform init -migrate-state |
Detect the prior backend, then offer to copy existing state into the new backend. |
This lab demonstrates -migrate-state end to end. Use a fresh copy of the lab directory if you also want to try -reconfigure separately.
Run migrate init and answer the prompt when Terraform asks whether to copy state:
terraform init -migrate-stateSample prompt:
Backend configuration changed!
Terraform has detected that the configuration specified for the backend
has changed. Terraform will now check for existing state in the backends.
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend to the
newly configured "local" backend. No existing state was found in the newly
configured "local" backend. Do you want to copy this state to the new "local"
backend? Enter "yes" to copy and "no" to start with an empty state.
Enter a value:Answer yes to copy state into state-new.tfstate. After migration, state-new.tfstate holds the managed resource records Terraform created during apply.
Choose -reconfigure instead when you deliberately want the new backend settings without copying state — for example when you are pointing at a fresh backend and do not need the old state file. Full remote backend setup (S3, HCP Terraform, and production patterns) is out of scope here.
-backend=false
-backend=false skips backend configuration during this init. It is mainly useful in a working directory that was already initialized for its backend. It is also useful when preparing an initialized dependency tree specifically for terraform validate without accessing the configured backend:
terraform init -backend=falseSample output:
Initializing provider plugins...
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/local v2.9.0
Terraform has been successfully initialized!The output omits Initializing the backend. HashiCorp documents this pattern for validation-only workflows:
terraform validateterraform init -backend=false prepares providers and modules without initializing the backend, then terraform validate checks the configuration — handy when you want to check configuration syntax before the backend is reachable.
-backend-config
Some teams split backend settings across files. Declare the backend type in configuration and pass remaining settings at init time.
versions.tf with a partial local backend:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
backend "local" {}
}local.tfbackend supplies the state file path — HashiCorp documents the *.backendname.tfbackend naming pattern:
path = "state/terraform.tfstate"Initialize with the extra backend settings:
terraform init -backend-config=local.tfbackendSample output:
Initializing the backend...
Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)
Terraform has been successfully initialized!You can also pass -backend-config=key=value multiple times. Remote backend design belongs in a dedicated state/backends lesson — here you only need to recognize the flag.
Initialize Terraform modules
terraform init also processes modules. Add a tiny local module and reference it from the root module.
Create modules/greet/main.tf:
resource "local_file" "greeting" {
content = var.message
filename = "${path.module}/greeting.txt"
}Create modules/greet/variables.tf:
variable "message" {
type = string
}Reference the module in main.tf:
module "greet" {
source = "./modules/greet"
message = "hello from module"
}Run init in the root module directory:
terraform initSample output:
Initializing the backend...
Initializing modules...
- greet in modules/greet
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)
Terraform has been successfully initialized!The Initializing modules phase records module metadata in .terraform/modules/modules.json.
- Remote module packages are downloaded under
.terraform/modules/ - For a relative local source such as
./modules/greet, Terraform refers to the existing module directory rather than maintaining a separate downloaded copy - Pass
-get=falsewhen you wantterraform initto skip child-module installation while still performing the other initialization steps
terraform init vs plan and validate
| Command | Main purpose | Needs init first | Downloads dependencies | Changes infrastructure |
|---|---|---|---|---|
terraform init |
Prepare working directory | No | Yes | No |
terraform validate |
Validate configuration | Normally initialized first | No | No |
terraform plan |
Preview changes | Yes | No | No |
Run terraform init once per machine per working directory (and again when dependencies change). Run terraform validate for a fast syntax and consistency check. Run terraform plan when you want to see what would change against current state.
Common terraform init errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Empty directory message | No .tf files present |
Add configuration files, or cd into the correct module root |
| Failed to query available provider packages | Bad version constraint or registry unreachable | Fix required_providers.version — see provider version constraints and lock file |
| Provider registry does not have a provider named … | Wrong source address in required_providers |
Correct the provider source — see Terraform providers |
| Backend initialization required | plan or apply before init with a custom backend |
Run terraform init in the module root |
| Unreadable module directory | module source path does not exist |
Fix the module path |
| Provider package checksum mismatch | Downloaded package does not match lock-file hashes | Refresh locks or fix mirrors — see provider version constraints and lock file |
Unavailable provider version
Set version = "= 99.0.0" in required_providers, then init:
terraform initSample output:
Error: Failed to query available provider packages
Could not retrieve the list of available versions for provider
hashicorp/local: no available releases match the given constraints 99.0.0Pick a published version or widen the constraint.
Incorrect provider source
Use a mistyped registry address in required_providers, then init:
terraform initSample output:
Error: Failed to query available provider packages
Could not retrieve the list of available versions for provider
hashicorp/nonexistent-provider-xyz: provider registry registry.terraform.io
does not have a provider named
registry.terraform.io/hashicorp/nonexistent-provider-xyzVerify the source value in required_providers.
Module source or download failure
Point a module block at a path that does not exist, then init:
terraform initSample output:
Error: Unreadable module directory
Unable to evaluate directory symlink: lstat does-not-exist: no such file or
directoryFix the source path in the module block.
References
- terraform init command
- Terraform workflow: init
- Dependency lock file
- Backend configuration
- Module sources
Summary
terraform init turns a folder of .tf files into a ready Terraform working directory on your machine:
- Configures the backend
- Downloads provider plugins
- Processes modules
- Writes
.terraform.lock.hcl - Stores metadata under
.terraform/
A second init on an unchanged configuration reuses installed plugins and lock selections instead of downloading everything again.
You practiced the natural workflow on Ubuntu: create configuration, run init, inspect .terraform/ and the lock file, rerun init, then confirm readiness with terraform providers and terraform validate.
You also saw when to init again — new directories, clones, dependency changes, and backend updates — and how flags such as -upgrade, -reconfigure, -migrate-state, -backend=false, and -backend-config adjust init behavior without replacing the core command.
The mistake to avoid is skipping init after a clone or dependency change. When init fails, match the error to provider source, version constraints, backend setup, or module paths; use the providers and lock-file guides for deeper troubleshooting.
Next in this course track: terraform validate, then terraform fmt, then terraform plan.

