Terraform

terraform init for Real Work and the Exam: Designing Backend and Provider Initialization Correctly

2026-04-19
NicheeLab Editorial Team

terraform init is the command that initializes your working directory all at once: it sets up the backend (where state is stored), resolves and downloads providers (the cloud SDK equivalents), fetches modules, and creates the dependency lock file.

The Associate exam frequently tests flags for reconfiguring the backend and migrating state, provider version constraints, and how to handle the lock file. Designing safe initialization and re-initialization flows is essential in real-world work too.

The Role of terraform init and the Overall Flow

terraform init reads your configuration files (main.tf and so on), resolves and downloads the required providers and modules, and initializes the backend. The results land in the .terraform directory and in .terraform.lock.hcl (the dependency lock file).

Execution breaks down into four phases: provider resolution, module fetching, backend initialization, and lock file generation/update. Use -upgrade when bumping versions, -reconfigure when rebuilding the backend, and -migrate-state when moving state.

  • Run it right after the first clone, and whenever you change a provider, module, or backend setting.
  • .terraform.lock.hcl pins the hashes of the selected provider binaries. As a rule, commit it to VCS to guarantee reproducibility.
  • -upgrade bumps versions to the latest within your constraints. Control the range via the version field in required_providers.
  • -reconfigure ignores the existing backend settings and re-initializes from scratch. Combine with -input=false for non-interactive migration.
PhaseWhat gets created/modifiedKey related flagsNotes
Provider resolution.terraform/providers, .terraform.lock.hcl-upgrade, -plugin-dir, -lockfile=readonlyLeverage TF_PLUGIN_CACHE_DIR in network-restricted environments
Module fetching.terraform/modules-upgradeModule versions can also be pinned via the source ref
Backend initializationRemote connection / lock verification-backend-config, -reconfigure, -migrate-stateNever hard-code credentials into code
Dependency lock update.terraform.lock.hcl-lockfile=readonly (suppress updates)Design CI so the lock file is not rewritten unexpectedly

Bird's-eye view of init

[main.tf] --parse--> (terraform init)
                      |---> [.terraform/providers]
                      |---> [.terraform/modules]
                      |---> [.terraform.lock.hcl]
                      \---> [Backend handshake + lock]

Minimal initialization example (local backend)

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  backend "local" {}
}

provider "aws" {
  region = var.region
}

variable "region" {
  type    = string
  default = "ap-northeast-1"
}

# 実行
# terraform init

Backend Initialization: from local to S3, azurerm, and gcs

The backend defines where Terraform stores its state file (tfstate). Team workflows almost always use a remote backend (S3, AzureRM, GCS, etc.) rather than local. S3 typically uses DynamoDB for locking, AzureRM uses Blob leases, and GCS prevents conflicts via object generation — each backend has its own concurrency mechanism.

When switching backends on an existing environment, re-initialize with -reconfigure and pair it with -migrate-state to move existing state safely. Never hard-code credentials or secrets in the backend block — pass them via -backend-config or environment variables.

  • S3 plus DynamoDB locking is the standard pattern for concurrent team execution.
  • -backend-config accepts both repeated key=value pairs and an HCL file path (-backend-config=path).
  • Forgetting -migrate-state when reconfiguring the backend can split state across locations.
  • In CI, use -input=false to enforce non-interactive execution.
Backend typeStorage locationLocking strategyOperational notes
localLocal fileNonePersonal experimentation only; not recommended for team use
s3S3 bucket + prefixDynamoDB tableAlways design bucket/table permissions and encryption carefully
azurermBlob Storage containerBlob leaseInject storage_account_name and similar values via backend-config
gcsGCS bucketObject generation-basedMatch service account permissions and bucket locations carefully

S3 backend and locking relationship

[Terraform CLI] --read/write--> [S3: tfstate]
         |                          \
         |---lock/unlock-----------> [DynamoDB: lock item]
         |
         \---credentials-----------> [IAM/環境変数/プロファイル]

S3 backend configuration and safe re-initialization

# main.tf(資格情報は直書きしない)
terraform {
  backend "s3" {}
}

# backend.hcl(例)
bucket         = "my-tf-state"
key            = "app/terraform.tfstate"
region         = "ap-northeast-1"
dynamodb_table = "tf-lock"
encrypt        = true

# 既存 local から S3 へ安全に移行(非対話)
terraform init \
  -reconfigure \
  -migrate-state \
  -input=false \
  -backend-config=backend.hcl

Provider Initialization and Version Pinning

Providers are the API clients for each platform. Based on required_providers, terraform init fetches the appropriate binaries from sources like the Terraform Registry, places them under .terraform/providers, and records their hashes in .terraform.lock.hcl.

Version constraints matter both in practice and on the exam. Use the ~> operator to express a compatibility range — tracking minor/patch releases while avoiding major upgrades. In CI, leverage TF_PLUGIN_CACHE_DIR to cut download time.

  • The default source for required_providers is registry.terraform.io/<namespace>/<name>.
  • .terraform.lock.hcl holds hashes per OS/CPU. If you target multiple platforms, generate the lock entries ahead of time with terraform providers lock.
  • -upgrade re-runs version selection within your constraints. Review the diff in a PR so you don't accidentally break reproducibility.
OperatorExampleResolved rangeUse case / caveats
== 5.6.05.6.0 onlyFull pin; useful for emergency pinning
~>~> 5.6.0>=5.6.0, <5.7.0Auto-track patch releases only
~>~> 5.0>=5.0.0, <6.0.0Pin the major version while tracking minor/patch releases
>=>= 5.5.05.5.0 or higherNo upper bound — risky; not recommended on the exam

Provider download and caching

[Terraform CLI] --resolve--> [Terraform Registry]
        |                          |
        |--download--> [$HOME/.terraform.d/plugin-cache] (任意)
        |                          |
        \------deploy-------------> [.terraform/providers]
                                   \-> [.terraform.lock.hcl]

Example: pinning providers and configuring the cache

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.6"
    }
  }
}

provider "aws" {
  region = var.region
}

# ダウンロードキャッシュ(シェル環境)
export TF_PLUGIN_CACHE_DIR="$HOME/.terraform.d/plugin-cache"
mkdir -p "$TF_PLUGIN_CACHE_DIR"

# 初期化(制約内で最新版へ)
terraform init -upgrade

Per-Environment Initialization Strategy: -backend-config and Reproducibility

When reusing the same code across dev/stg/prod, split backend settings and credentials into per-environment files and inject them with -backend-config. Use -reconfigure for changes that require re-initialization, and always pair it with -migrate-state when the state location changes.

In CI, force non-interactive runs with -input=false and prevent unexpected lock file updates with -lockfile=readonly.

  • Separate storage location and lock table per environment via backend-<env>.hcl files.
  • Issue credentials via environment variables or Vault/OIDC — never put them in code.
  • Pin providers to the same version across environments and absorb the differences with input variables.
FlagPrimary useExam takeawayOperational notes
-reconfigureRe-initialize the backendIgnore existing settings and renegotiateCombine with -input=false to skip prompts
-migrate-stateMigrate state to a new backendMandatory for safe migrationThere is no dry-run mode — back up state beforehand
-backend-configInject secrets / environment differencesAvoid hard-coding in codeFile path and key=value can be combined
-upgradeUpdate providers/modulesSelects the newest version within constraintsReview the diff in a PR
-lockfile=readonlyPrevent lock file updatesPreserve reproducibilityMake it the CI default to reduce accidents

Initializing one codebase across multiple environments

[repo]
  |-- main.tf
  |-- backend-dev.hcl
  |-- backend-stg.hcl
  |-- backend-prd.hcl

[CLI] --dev--> terraform init -backend-config=backend-dev.hcl
[CLI] --stg--> terraform init -backend-config=backend-stg.hcl
[CLI] --prd--> terraform init -backend-config=backend-prd.hcl

Per-environment backends and init commands for CI

# backend-stg.hcl
bucket         = "my-tf-state-stg"
key            = "app/terraform.tfstate"
region         = "ap-northeast-1"
dynamodb_table = "tf-lock-stg"

# backend-prd.hcl
bucket         = "my-tf-state-prd"
key            = "app/terraform.tfstate"
region         = "ap-northeast-1"
dynamodb_table = "tf-lock-prd"

# ステージングを初期化(ロックファイルは読み取り専用)
terraform init \
  -backend-config=backend-stg.hcl \
  -input=false \
  -lockfile=readonly

# 本番へ backend 切替と state 移行(CI)
terraform init \
  -reconfigure \
  -migrate-state \
  -backend-config=backend-prd.hcl \
  -input=false \
  -lockfile=readonly

Troubleshooting and Exam Hotspots

The classic problems are forgetting -migrate-state during backend reconfiguration, provider version mismatches, and download failures in network-restricted environments. Don't take error messages at face value — always check three things: the .terraform directory, .terraform.lock.hcl, and the backend configuration.

On the Associate exam, expect questions about which flags safely initialize or re-initialize a project, how to handle the lock file, and how to inject credentials securely.

  • Whenever you change the backend, think of -reconfigure and -migrate-state as a pair.
  • Sudden provider resolution failures usually call for -upgrade or a diff check on the lock file.
  • In air-gapped environments, work around it with TF_PLUGIN_CACHE_DIR plus a mirror or proxy.
SymptomMessage excerptLikely causeTypical fix
plan references a different state after a backend switchBackend configuration changed-migrate-state was not runterraform init -reconfigure -migrate-state
Provider resolution failureFailed to query available provider packagesVersion constraints / networkReview constraints, run -upgrade, configure caching
Lock contentionError acquiring the state lockAnother run is holding the lockWait, then verify the DynamoDB lock is released (force-unlock is a last resort)
You want to refuse lock file updatesattempting to write lock fileUnexpected update in CIPrevent it with -lockfile=readonly

Common failure pattern (forgotten backend migration)

[local backend] --(state 残存)--> [開発者PC]
         \
          X  terraform init(-migrate-state なし)
           \
            --> [S3 backend]  --結果: state が分断/競合

Commands commonly used for recovery and verification

# backend 再交渉 + state 安全移行
terraform init -reconfigure -migrate-state -backend-config=backend.hcl -input=false

# provider を制約内で再選定
terraform init -upgrade

# 異種プラットフォーム向けにロック事前生成(複数 OS/CPU)
terraform providers lock -platform=linux_amd64 -platform=darwin_amd64

# どうしても壊れたときの最終手段(要注意)
# rm -rf .terraform && terraform init

Check Your Understanding

Associate

問題 1

You are migrating an existing project from a local backend to an S3 backend (with DynamoDB locking). You need to safely move the existing state and run non-interactively in CI. Which command is most appropriate?

  1. terraform init -reconfigure -migrate-state -input=false -backend-config=backend.hcl
  2. terraform init -upgrade
  3. terraform init -reconfigure (this flag only)
  4. Run terraform state push s3://bucket/key, then terraform init

正解: A

Backend re-initialization requires -reconfigure, and -migrate-state is needed to safely move existing state to the new backend. -input=false makes the run non-interactive in CI, and -backend-config injects secrets. -upgrade only updates providers/modules and does not meet the migration requirement. terraform state push is not the recommended approach for this kind of migration.

Frequently Asked Questions

When should I run terraform init?

Run it right after cloning a repository for the first time, whenever you change provider, module, or backend settings or version constraints, and after deleting the .terraform directory. It is a prerequisite for plan/apply.

Should .terraform.lock.hcl be committed to VCS?

Yes — it is recommended for root modules. Committing it pins the provider binary hashes and guarantees reproducible builds. Reusable module repos sometimes exclude it depending on policy, but team root code should commit it as standard practice.

Is it OK to put access keys in the backend block?

No, it is not recommended. Inject credentials and secrets via -backend-config, environment variables, profiles, or OIDC/Federation. The exam expects you to know that hard-coding secrets into Terraform code is wrong.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.