Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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 .tfvars files, and the .terraform directory 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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

  1. Change the version constraint in a dedicated branch.
  2. Run terraform init -upgrade to pick up provider versions within the constraint and update .terraform.lock.hcl.
  3. Run terraform plan and read every create, update, and replace action that appears without a corresponding code change.
  4. 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.

  1. Check formatting with terraform fmt -check -recursive.
  2. Run terraform init so providers install from the committed lock file, with backend credentials injected by the CI platform.
  3. Run terraform validate.
  4. Run terraform plan -out=tfplan against 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.
  5. 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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Investigate with a refresh-only plan

  1. In the working directory for the affected state, run terraform plan -refresh-only.
  2. Read the proposed state updates. They show how Terraform would record the live values. The plan does not propose changes to the infrastructure itself.
  3. Decide whether each change is intentional or not, using the table below.
  4. 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.