iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Good Terraform practice on a team comes down to one operating model. Configuration is reviewed and versioned. State is shared and protected. Modules have clear interfaces. Provider and module upgrades happen deliberately. Every plan shows what is about to change, including changes made outside Terraform. Drift is resolved through a reviewed code change or a deliberate infrastructure correction, never by letting whoever last touched the console silently win.
The sections below follow the order a team usually needs these pieces: state first, then sensitive data handling, module boundaries, dependency pinning, the pull request pipeline, and finally drift.
Start with shared, protected state
Terraform state records which real objects your configuration manages, and every plan is calculated against it. A local terraform.tfstate file works for one person on a small project. It breaks down as soon as two engineers or a pipeline need to change the same infrastructure. HashiCorp’s Terraform documentation on state makes the point directly: “Remote state is the recommended solution to this problem.” That sentence addresses the team situation, where everyone working on a configuration needs the same state, and it is the baseline for everything that follows.
What a team backend must provide
HashiCorp recommends HCP Terraform or a remote backend for secure collaboration. Whichever you choose, confirm four properties before you commit to it:
#1 Best Overall
- Locking. Prevents two runs from writing the same state at once. Backends do not all implement locking the same way.
- Recovery. Versioned copies or backups that let you roll back a bad or corrupted write.
- Access control and auditability. Who can read and write state, and whether those operations are logged.
- Encryption and key control. Whether state is encrypted at rest and who controls the keys.
HashiCorp’s documentation covers HCP Terraform, Consul, S3, Azure Blob Storage, Google Cloud Storage, and other remote options. Feature support differs between them, so check each property against the reference page for the backend you pick rather than assuming parity.
S3 as a worked example and the locking change
S3 is a common self-managed choice. The S3 backend reference, as published at the time of writing, rates bucket versioning as highly recommended, which gives you a recovery path for corrupted or accidentally overwritten state. It also documents S3 lockfiles through use_lockfile and marks DynamoDB-based locking as deprecated.
terraform {
backend "s3" {
bucket = "example-terraform-state"
key = "network/prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true # requires a Terraform version that supports S3 lockfiles
}
}
The locking options compare as follows:
| Locking method | Status in the S3 backend reference | Action |
|---|---|---|
DynamoDB table (dynamodb_table) |
Deprecated | Schedule a move off DynamoDB locking in a planned change window |
S3 lockfile (use_lockfile = true) |
Documented as the current option | Enable it on Terraform versions that support it; the backend reference states the requirement |
When a remote write fails
A remote backend reduces routine local copies of state, but it does not remove failure modes. What happens when a state write fails, including any local copy Terraform keeps, depends on the backend. Work out that failure path before you need it. Decide which copy is authoritative, who may restore a prior version, and how you will confirm the restored state against the live environment with a plan.
Treat state and saved plans like credentials
State and plan files can contain credentials and other sensitive attribute values, because Terraform stores what resources return. Handle them accordingly.
- Keep
terraform.tfstate, its backups, saved plan files, sensitive.tfvarsfiles, and the.terraformdirectory out of source control. - Restrict state access more tightly than source-code access. Only the automation identity and the administrators who need recovery access should be able to read or write the state store.
- Encrypt state at rest where the backend supports it, and audit access where the platform allows.
- Keep credentials out of backend configuration values. Terraform persists those values locally, so supply them through the CI platform’s secret handling or dynamic credentials.
Marking a variable or output sensitive = true hides the value in CLI output, but it does not encrypt the value stored in state. Encryption and access control are what protect state; the sensitive flag only controls display.
Structure modules around ownership and interfaces
A module earns its place when it has one coherent responsibility and an interface that consumers can understand. The boundary questions mirror the state boundary questions: who owns the code, which resources change together, and how often each piece changes.
Root modules and child modules
Keep each root module focused on one deployable stack or environment. Move infrastructure patterns that repeat, or that have a meaningful interface, into child modules. Avoid modules that wrap a single resource without adding a stable abstraction, because they add indirection without adding much control. Each module should document its required inputs, its outputs, its assumptions, and the provider versions it supports.
Free tools Windows power users keep installed
One-click scans. No signup required.
Fix shared values and expose environment choices
Google Cloud’s guidance on Terraform root modules for cloud infrastructure recommends hard-coding inputs that every deployment of a service module shares, and requiring environment-specific inputs as variables. In practice, a development and a production root module usually differ in a short list of values, such as instance sizing, replica counts, or domain names. Everything else should follow the same code path. Hard-coded shared values also mean that a change to them goes through the module’s review process rather than appearing as a one-line override in one environment.
Sharing values between state files
Use separate state for each independently managed part of the estate, then share values deliberately. Remote state can expose a root module’s outputs to another configuration. That is convenient, but it creates a dependency and an access relationship: the consuming configuration needs read access to the producer’s state, and every output becomes an interface you must keep stable. Where a provider or cloud service offers a suitable data-sharing mechanism, such as a data source or a managed parameter store, that can be a cleaner boundary. Choose based on how your teams are allowed to access each other’s state.
Rank #4
Pin the toolchain and dependencies
Many unexpected plan changes come from a dependency that moved underneath an unrelated change. Pinning makes those movements visible and reviewable.
Core and provider constraints
- Root configurations should declare a Terraform core version constraint that matches the versions your CI image supports, and a provider source with a version constraint tight enough to control upgrades.
- Reusable modules should state the minimum versions they need, so they do not force consumers onto an unnecessarily narrow range.
Commit the lock file and review its changes
Commit .terraform.lock.hcl. It records the selected provider versions and their hashes, so every workstation and pipeline installs the same provider builds. Review changes to the lock file in the same pull request as the configuration change that caused them. The lock file covers providers only; it does not record which version of a remote module you selected.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Pin remote modules explicitly
Because the lock file does not cover module selections, set the version on every external module block, or use a bounded range you actively manage. Without that, a new release from a module publisher can change your plans with no diff in your repository.
module "network" {
source = "example-org/network/cloud" # illustrative source
version = "~> 5.0"
}
Run upgrades as their own change
- Change the version constraint in a dedicated branch.
- Run
terraform init -upgradeto pick up provider versions within the constraint and update.terraform.lock.hcl. - Run
terraform planand read every create, update, and replace action that appears without a corresponding code change. - Merge the upgrade separately from unrelated infrastructure changes, so that a rollback removes only the upgrade.
Automate the pull request pipeline without auto-applying everything
A reviewable pipeline validates the change, produces a plan, shows that plan to a person, and applies only under your approval controls. The exact commands, approval gates, and policy tooling depend on your Terraform version, backend, and CI platform, so treat the steps below as a shape to adapt rather than a universal recipe.
- Check formatting with
terraform fmt -check -recursive. - Run
terraform initso providers install from the committed lock file, with backend credentials injected by the CI platform. - Run
terraform validate. - Run
terraform plan -out=tfplanagainst the intended workspace or state, and post a readable rendering of the plan to the pull request. Keep the saved plan as a short-lived artifact. - After approval, run
terraform apply tfplan. Terraform refuses to apply a saved plan that no longer matches the current state, so regenerate and review the plan rather than forcing it through.
Add policy checks where the organization needs hard limits on what may be provisioned, such as approved regions or instance classes. Policy results should block the apply step when they fail, the same way a rejected approval does.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Detect drift and decide which description wins
Drift means the live infrastructure no longer matches what Terraform last recorded, or what your configuration declares. Terraform refreshes resource attributes during every ordinary plan and apply, so it always compares against current reality. A normal plan, however, also proposes changes to bring infrastructure back to configuration, which is a different question from “what changed outside Terraform?” For that question, use a refresh-only plan.
Investigate with a refresh-only plan
- In the working directory for the affected state, run
terraform plan -refresh-only. - Read the proposed state updates. They show how Terraform would record the live values. The plan does not propose changes to the infrastructure itself.
- Decide whether each change is intentional or not, using the table below.
- If you accept the recorded updates after review, run
terraform apply -refresh-only. This writes the accepted changes to state and does not change infrastructure.
HashiCorp’s Terraform drift tutorial, “Manage resource drift,” states the boundary plainly: “A refresh-only operation does not attempt to modify your infrastructure to match your Terraform configuration — it only gives you the option to review and track the drift in your state file.”
Choose the authoritative description
| Finding | Decision | Next action | Confirm by |
|---|---|---|---|
| Intended live change, such as a capacity increase made during an incident | Adopt it | Update the configuration to capture the change | A normal terraform plan shows no changes for that resource |
| Accidental or unauthorized change | Restore the declared configuration | Run a normal, reviewed plan that reverts the change. Check it for replace actions first, because some corrections recreate a resource and disrupt the service it runs | A post-apply plan is clean |
| Existing resource that Terraform does not manage | Bring it under management | Investigate an import workflow rather than creating a duplicate resource | The plan no longer proposes creating that resource |
| Change that must stay outside Terraform | Documented exception | Record an owner and a review date, and revisit the exception on that date | The exception record exists and is current |
Scheduled checks in HCP Terraform
For recurring checks rather than one-off investigations, HCP Terraform health assessments run non-actionable refresh-only plans. They can identify drift without changing the state or the infrastructure. HashiCorp’s drift tutorial states that drift detection is available in HCP Terraform Standard Edition. Edition entitlements change over time, so confirm them in your HCP Terraform account before you rely on scheduled drift checks. If you run a self-managed backend, you can schedule terraform plan -refresh-only from your own CI system, but you will also need to build the alerting and reporting yourself.
Compare drift approaches
| Approach | What it does | Changes state? | Changes infrastructure? | Basis in official Terraform sources |
|---|---|---|---|---|
| On-demand refresh-only plan | Shows the state updates Terraform would record | Only if you apply the accepted refresh-only result | No | Terraform CLI and the “Manage resource drift” tutorial |
| Scheduled managed health assessment in HCP Terraform | Runs non-actionable refresh-only plans on a schedule | No | No | HashiCorp drift tutorial; drift detection listed under Standard Edition |
| Third-party continuous discovery and remediation | Varies by product | Varies by product | Varies by product | Not covered here. Evaluate any product on whether it writes to state, whether it applies changes automatically, and how it handles approvals |
Whichever approach you choose, the principle is the same: observed drift becomes a reviewed decision, and the decision is recorded either in configuration or in a documented exception.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.

