terraform init
Learn exactly what terraform init does, downloading providers, setting up backends, creating the lock file, and how to use init flags like -upgrade and -reconfigure.
What Is terraform init?
terraform init is the very first command you run in any Terraform project. It prepares your working directory so that every other Terraform command (plan, apply, destroy) can work correctly.
Think of it like npm install or pip install, it sets up the dependencies your project needs before you can do anything else.
terraform init
What terraform init Does Internally
When you run terraform init, Terraform does three things:
1. Downloads Provider Plugins
Terraform reads the required_providers block in your configuration and downloads the declared providers from the Terraform Registry (or a private registry):
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "3.6.0"
}
}
}
Running init on this configuration downloads both the AWS and Random providers into .terraform/providers/. This directory can be hundreds of megabytes, always add it to .gitignore.
Sample output:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Finding hashicorp/random versions matching "3.6.0"...
- Installing hashicorp/aws v5.54.1...
- Installing hashicorp/random v3.6.0...
Terraform has been successfully initialized!
2. Sets Up the Backend
If you have a backend block configured, init connects to it and migrates state if needed:
terraform {
backend "s3" {
bucket = "my-tfstate-bucket"
key = "production/terraform.tfstate"
region = "us-east-1"
}
}
When switching from local state to a remote backend, init asks:
Do you want to copy existing state to the new backend?
yes/no:
3. Creates the Lock File
terraform init writes .terraform.lock.hcl, a file that pins the exact provider versions downloaded:
# .terraform.lock.hcl (auto-generated)
provider "registry.terraform.io/hashicorp/aws" {
version = "5.54.1"
constraints = "~> 5.0"
hashes = [
"h1:abc123...",
]
}
Always commit .terraform.lock.hcl to Git. It ensures every team member and every CI/CD pipeline uses exactly the same provider versions, preventing "works on my machine" failures caused by version drift.
The .terraform Directory
After init, your project will have:
my-project/
├── main.tf
├── variables.tf
├── .terraform/ # ← created by init, add to .gitignore
│ ├── providers/
│ │ └── registry.terraform.io/
│ │ └── hashicorp/
│ │ └── aws/5.54.1/ # downloaded provider binary
│ └── terraform.tfstate # backend config cache
└── .terraform.lock.hcl # ← commit this to Git
Common init Flags
-upgrade
Re-download all providers, selecting the newest version that satisfies your constraints:
terraform init -upgrade
Use this when you want to update providers to the latest allowed version. Without -upgrade, init uses the versions already in the lock file.
Warning: Always run
terraform planafter-upgradeto check for breaking changes in the new provider version.
-reconfigure
Force init to reconfigure the backend without migrating state:
terraform init -reconfigure
Useful when you have changed your backend configuration and want to switch without copying existing state.
-backend=false
Skip backend initialisation entirely:
terraform init -backend=false
Useful in CI pipelines where you only want to validate the configuration without connecting to a remote backend.
-backend-config
Pass backend configuration values at init time instead of hardcoding them in the backend block, great for keeping bucket names or credentials out of source code:
terraform init \
-backend-config="bucket=my-tfstate-bucket" \
-backend-config="key=production/terraform.tfstate" \
-backend-config="region=us-east-1"
When to Re-run init
You must re-run terraform init whenever you:
| Change | Why |
|---|---|
Add or remove a required_providers entry | New provider needs to be downloaded |
Change provider version constraints | Different version needs to be installed |
Change the backend block | Backend connection must be re-established |
| Clone a repo for the first time | Providers are not committed to Git |
Add or update a module source | Module code needs to be downloaded |
It is always safe to re-run init, it never modifies your infrastructure.
init in CI/CD Pipelines
In a CI/CD pipeline, always run init before plan or apply. Use the -input=false flag to prevent interactive prompts:
# GitLab CI example
terraform-plan:
script:
- terraform init -input=false
- terraform plan -out=tfplan
Knowledge Check
Question 1: Lock File Purpose
After running terraform init, you see a .terraform.lock.hcl file. A teammate asks if they should commit it. What do you tell them?
Question 2: When to Re-run init
You add a new provider to required_providers in your main.tf. You run terraform plan immediately. What happens?
Question 3: terraform init -upgrade
Your lock file pins AWS provider at v5.0.0. A new version v5.54.1 is available. You want to update. What command do you run?
Frequently Asked Questions
What does terraform init actually do?
Four things. It downloads the provider plugins your configuration requires into .terraform/, fetches any external modules, configures the backend where state will be stored, and writes or verifies .terraform.lock.hcl with the exact provider versions and checksums. Until it has run, no other Terraform command works, because Terraform has no providers loaded and does not know where state lives.
When do I need to re-run terraform init?
Whenever the things it sets up change: after adding or changing a provider or its version constraint, after adding or updating a module source, after changing the backend configuration, and after cloning a repository for the first time since .terraform/ is not committed. Terraform tells you when it is needed with an error saying the configuration has changed since initialisation. Running it again when it is not needed is harmless.
What is the difference between terraform init and terraform init -upgrade?
Plain init respects .terraform.lock.hcl and installs exactly the provider versions recorded there, which is what makes builds reproducible. -upgrade ignores the locked versions, resolves the newest releases allowed by your version constraints, and rewrites the lock file. Use -upgrade deliberately when you intend to move provider versions, then review the lock file diff like any other dependency bump, never as a reflex when something is failing.
Should I commit the .terraform directory?
No. It contains downloaded provider binaries which are large, platform-specific and reproducible from the lock file, so committing them bloats the repository for no benefit. Add .terraform/ to .gitignore. Do commit .terraform.lock.hcl though, that is the small text file recording which versions to install, and it is what makes everyone's init produce the same result.
How do I run terraform init in CI?
Run it at the start of every job, since CI workspaces start empty. Use -input=false so it never waits for interactive input, and -backend-config to supply backend settings from environment variables or secrets rather than committing them. Caching the .terraform directory between runs speeds things up noticeably on large provider sets, but key the cache on the lock file so a provider change invalidates it.
Why does init fail with a provider registry error?
Usually network access or a version constraint that cannot be satisfied. In a restricted network the registry may be unreachable, in which case a provider mirror or a network policy change is needed. If the error names a version, your constraints conflict. Perhaps a module requires a provider version your root configuration excludes. Read the constraint chain in the error, which names each source, and reconcile them in required_providers.