Definition
- terraform init -- the bootstrap command that prepares a working directory by downloading provider plugins, initializing the backend, and downloading modules.
- Provider plugins -- binary executables that know how to talk to a specific cloud provider's API, downloaded into .terraform/providers.
- .terraform.lock.hcl -- a lock file pinning exact provider versions and checksums, committed to Git so every engineer gets identical binaries.
- Remote state -- storing the state file in a shared backend like S3 so every engineer and CI runner reads the same truth.
- State locking -- using a DynamoDB table as a lock so only one apply can run against a given state at a time.
- State isolation -- giving each environment and service its own state key so blast radius stays small.
Think of terraform init as setting up a new teller's workstation at National Bank of India before the branch opens. You install the cash-counting machine (provider plugins), connect the terminal to the central vault ledger (the remote backend), bring in the standard forms everyone uses (modules), and arrange it all in the drawer (the .terraform folder). Running Terraform safely across a team is like the vault rule that only one person can be inside at a time, holding the one key (state locking), and that the retail team cannot touch the corporate vault (state isolation).
Understanding the Concepts
What does terraform init do?
terraform init is the first command you run in any new or freshly cloned Terraform directory. It is safe to run many times. Its first job is provider installation -- your configuration declares providers like aws or google, and init downloads the matching plugin binaries from the Terraform registry into .terraform/providers. It also writes a lock file, .terraform.lock.hcl, that records exact versions and checksums.
terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.31.0...
- Installed hashicorp/aws v5.31.0 (signed by HashiCorp)
Terraform has been successfully initialized!
How does the lock file work?
The .terraform.lock.hcl file pins the exact provider version and hash so every engineer and the CI runner install byte-identical plugins. You commit this file to Git. The .terraform directory itself is a local cache that you never commit because init can always rebuild it.
# .terraform.lock.hcl -- committed to Git
provider "registry.terraform.io/hashicorp/aws" {
version = "5.31.0"
constraints = "~> 5.0"
hashes = [
"h1:abc123...",
"zh:def456...",
]
}
# .gitignore
.terraform/
*.tfstate
*.tfstate.backup
How does remote state work?
By default, state is a local file, which is useless for a team because only one person has it. You configure a remote backend, typically S3, so everyone reads and writes the same state. The backend block goes in your Terraform configuration.
# backend.tf
terraform {
backend "s3" {
bucket = "nbi-tf-state"
key = "ledger/production/terraform.tfstate"
region = "ap-south-1"
dynamodb_table = "nbi-tf-locks"
encrypt = true
}
}
# When you run init, Terraform connects to the remote backend
terraform init
Initializing the backend...
Successfully configured the backend "s3"!
Terraform will automatically use this backend.
How does state locking prevent corruption?
If two engineers run apply at the same time against the same state, they can corrupt it. Terraform uses a DynamoDB table as a lock -- before an apply, it writes a lock record. If the record already exists, your command waits or fails with a message that the state is locked.
# Engineer A runs apply
terraform apply
# Terraform acquires lock in DynamoDB
# Engineer B tries apply at the same time
terraform apply
Error: Error locking state: Error acquiring the state lock
Terraform acquires a state lock to protect the state from being
written by multiple users at the same time.
Lock Info:
ID: abc-123-def-456
Who: rajesh@nbi-laptop
Operation: OperationTypeApply
Created: 2024-01-15 10:30:00 UTC
How do you isolate state to keep blast radius small?
You do not put all environments in one giant state file. Instead, you give each environment and each service its own state key. A common pattern is one state file per environment per service. This way a staging mistake cannot reach production, and unrelated teams never block each other.
nbi-tf-state/
ledger/staging/terraform.tfstate
ledger/production/terraform.tfstate
payments/staging/terraform.tfstate
payments/production/terraform.tfstate
fraud/staging/terraform.tfstate
fraud/production/terraform.tfstate
# staging backend
terraform {
backend "s3" {
bucket = "nbi-tf-state"
key = "ledger/staging/terraform.tfstate"
region = "ap-south-1"
}
}
# production backend -- completely separate state
terraform {
backend "s3" {
bucket = "nbi-tf-state"
key = "ledger/production/terraform.tfstate"
region = "ap-south-1"
}
}
How do reusable modules enforce secure defaults?
Instead of every team copy-pasting database configuration, the platform team publishes a module with secure defaults -- encryption on, public access off, backups enabled. Each service team calls it with their own values, pinning the module version with a ref tag so upgrades are deliberate.
# modules/rds-postgres/main.tf -- platform team's module
resource "aws_db_instance" "this" {
identifier = var.identifier
engine = "postgres"
engine_version = var.engine_version
instance_class = var.instance_class
allocated_storage = var.storage_gb
storage_encrypted = true # enforced
publicly_accessible = false # enforced
backup_retention_period = 7 # enforced minimum
skip_final_snapshot = false # enforced
tags = var.tags
}
# ledger team calls the module
module "ledger_db" {
source = "git::https://github.com/nbi/tf-modules.git//modules/rds-postgres?ref=v2.1.0"
identifier = "nbi-ledger-db"
engine_version = "15.4"
instance_class = "db.r6g.large"
storage_gb = 100
tags = { Service = "account-ledger" }
}
How does CI-driven plan and apply work?
Engineers never run apply from their laptops against production. They open a pull request, the CI pipeline runs terraform plan and posts the diff as a comment, a human reviews and approves, and the pipeline runs terraform apply using a narrowly scoped IAM role with short-lived credentials.
__CODE_BLOCK_13__
\n' + plan + '\n```'
});
__CODE_BLOCK_14__
text
# WRONG -- .terraform committed to Git
git add .terraform/
# CORRECT -- .terraform in .gitignore, lock file committed
git add .terraform.lock.hcl
__CODE_BLOCK_15__
hcl
# WRONG -- no locking configured
terraform {
backend "s3" {
bucket = "nbi-tf-state"
key = "everything/terraform.tfstate" # one giant state
}
}
# CORRECT -- locking + isolated state
terraform {
backend "s3" {
bucket = "nbi-tf-state"
key = "ledger/production/terraform.tfstate"
dynamodb_table = "nbi-tf-locks"
encrypt = true
}
}
```
What should a senior engineer know?
A senior engineer knows that the CI role should be scoped narrowly with short-lived credentials through OIDC, never long-lived access keys. They know that Terraform workspaces can also separate state but most teams prefer explicit separate backend keys because it is more visible. They know that init is safe to run repeatedly, that the lock file must be committed, and that modules should be versioned with ref tags so upgrades are deliberate, not accidental.
Visual Overview
terraform init
──────────────
┌───────────────────┐
│ terraform init │
└─────────┬─────────┘
│
┌───────┴────────┬──────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Download │ │ Initialize │ │ Download │
│ providers │ │ backend │ │ modules │
│ (aws,gcp) │ │ (S3 remote) │ │ (rds-postgres)│
└─────┬────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└───────┬───────┘──────────────────┘
▼
┌──────────────────────────┐
│ .terraform/ │
│ ├── providers/aws/ │ local cache (never commit)
│ ├── modules/rds-postgres/│
│ └── terraform.tfstate │ (if local backend)
├──────────────────────────┤
│ .terraform.lock.hcl │ version pins (commit this)
└──────────────────────────┘
State Safety:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Rajesh │ │ Priya │ │ CI │
│ (laptop) │ │ (laptop) │ │ (runner) │
└─────┬────┘ └─────┬────┘ └─────┬────┘
│ │ │
└───────┬───────┘───────────────┘
▼
┌──────────────────────────┐
│ S3: nbi-tf-state │ shared remote state
│ DynamoDB: nbi-tf-locks │ only one apply at a time
└──────────────────────────┘
Key Takeaway
The terraform init command bootstraps a working directory by downloading provider plugins, initializing the backend, and downloading modules into the .terraform folder. The lock file pins exact provider versions so every engineer and CI runner installs identical binaries, and you commit it to Git while keeping the .terraform directory in .gitignore.
Making Terraform safe for a whole organization comes down to five things. Remote state in S3 so everyone reads and writes the same record. State locking with DynamoDB so only one apply can run at a time. State isolation so each environment and service has its own state key, keeping blast radius small. Reusable modules with secure defaults so no team forgets to encrypt a customer database. And CI-driven plan and apply with pull-request review so nobody applies from a laptop against production.
At National Bank of India this setup let three teams -- ledger, payments, and fraud -- work in parallel without ever corrupting shared state or stepping on each other. The lock file ensured everyone had identical provider binaries, the DynamoDB lock prevented two engineers from applying at the same time, and the CI pipeline posted the plan diff on every pull request so a teammate could review infrastructure changes just like code.
Remember one sentence: init downloads plugins and initializes the backend, and safe teamwork needs remote state with locking, isolation per environment, reusable modules, and CI-gated applies.
---