Infrastructure as Code (IaC) with Terraform/Ansible

Chapter 4
Terraform State and Resource Lifecycle

Post Graduate Program in Dev Ops
Infrastructure as Code (IaC) with Terraform/Ansible   •   Post Graduate

4. Terraform State and Resource Lifecycle

Introduction and Context

Terraform is often introduced as a declarative tool: engineers describe the desired infrastructure, and Terraform determines how to create or modify resources to reach that desired condition. This description is accurate but incomplete. Terraform does not operate only from configuration files. It also relies on a record of what it believes it has created and how those objects correspond to configuration. That record is the Terraform state.

State is therefore the operational memory of a Terraform system. It connects configuration, provider data, and real infrastructure. It enables Terraform to calculate a plan rather than recreate every object on each run. In an individual experiment, state may be a local file. In a production environment, however, it becomes a shared, security-sensitive, and governance-critical artifact. Decisions about where state is stored, how it is locked, who may access it, and how it is recovered directly affect reliability and organizational risk.

This chapter examines state management and the Terraform resource lifecycle as one integrated problem. It explains local and remote state, backends and locking, resource addressing, import and move operations, dependency graphs, drift, replacement risk, and recovery practices. The discussion assumes Terraform’s declarative model and places particular emphasis on collaborative DevOps environments in which infrastructure changes must be reviewable, repeatable, and safe.

Learning Objectives

By the end of this chapter, a student should be able to:

1. Explain the purpose, structure, and limitations of Terraform state.

2. Distinguish local state from remote backends and evaluate locking strategies.

3. Interpret resource addresses, instances, dependencies, and planned actions.

4. Import existing infrastructure and move resources without unintended recreation.

5. Diagnose drift, replacement risks, state corruption, and failed operations.

6. Design secure and collaborative state-management practices.

7. Apply state-recovery and lifecycle methods to realistic operational scenarios.

Terraform State as a Reconciliation Record

A Terraform configuration expresses desired infrastructure. The provider exposes an external system such as a cloud platform, DNS service, or Kubernetes cluster. State records Terraform’s understanding of the relationship between the configuration and objects managed through the provider. During planning, Terraform compares configuration with state and, where necessary, refreshes relevant information from the provider. It then proposes actions that should reconcile the known world with the desired world.

State commonly contains resource instances, provider-related information, attributes returned by APIs, dependencies, and identifiers used to locate real objects. It may also contain sensitive values. Although state is represented in a machine-readable format, it should not be treated as a harmless cache or ordinary build output. A password, certificate material, token, database connection string, or other secret can appear in state even when it is not visible in a plan or configuration file.

The state file is not the infrastructure itself. Deleting state does not delete cloud resources; it deletes Terraform’s record of them. Conversely, manually changing a state file does not reliably change the corresponding infrastructure. This distinction is central to safe operations. Configuration and state should normally be changed through Terraform commands and controlled code review rather than by editing the serialized file directly.

Terraform’s model can be represented as a reconciliation loop:

Configuration + current state + provider observations → plan → apply → updated state.

The loop is useful, but it is not omniscient. Provider APIs may be eventually consistent, attributes may be computed, external teams may change resources, and failed operations may leave partial results. Terraform state is an informed model of infrastructure, not an infallible representation of reality.

Local State, Remote State, and Backends

By default, Terraform can maintain state locally in a state file in the working directory. Local state is convenient for learning, prototypes, isolated experiments, and personal development. It avoids service configuration and makes initial experimentation simple. Its weaknesses become serious when more than one person or automation process operates on the same infrastructure.

A local file is easy to overwrite, copy into an insecure location, or use concurrently. It may be omitted from version control, producing an incomplete repository, or accidentally committed, exposing identifiers and secrets. Two engineers can each plan from different versions of state and then apply conflicting changes. Local state also provides limited auditability and recovery unless the team builds additional controls.

A remote backend stores state in a shared service or storage system. Examples include object storage with a locking mechanism, Terraform Cloud or Terraform Enterprise, and other supported backend services. Remote state allows a team and its CI/CD systems to use a common authoritative record. A suitable backend can provide encryption, versioning, access control, locking, audit trails, and recovery capabilities. These properties are not automatic; they depend on configuration and the capabilities of the selected platform.

A backend has two distinct responsibilities. First, it defines where state is stored and how Terraform communicates with it. Second, it may provide operational features such as locking, state version history, encryption, and controlled execution. Backend configuration can often be parameterized for different environments, but the distinction between backend configuration and ordinary provider configuration must be understood. Backend credentials should not be embedded casually in source code or exposed in command-line history.

State separation is an architectural decision. A team may use separate state files for development, testing, staging, and production, or for distinct infrastructure domains such as networking, identity, and application platforms. Excessive separation can create cross-state coordination complexity; insufficient separation can enlarge the blast radius of a mistake. The appropriate boundary usually follows ownership, access-control needs, lifecycle differences, and dependency relationships.

Locking and Collaborative Execution

State locking prevents multiple operations from modifying the same state simultaneously. A lock is acquired before an operation that may write state and released after completion. Without locking, two applies can read the same starting state, produce incompatible changes, and allow the later write to overwrite the earlier state update. The infrastructure may then exist in a condition that the recorded state no longer describes accurately.

Locking is not the same as approval. A lock coordinates technical concurrency; it does not determine whether a change is authorized. In mature teams, locking is combined with pull requests, policy checks, protected environments, identity-based access, and an auditable CI/CD workflow.

A lock can remain after a process crashes or a network failure interrupts communication. Force-unlocking is consequently a recovery operation, not a routine way to resolve impatience. Before removing a lock, an operator should establish that no Terraform process is still running, inspect job logs, identify the lock owner and operation, and document the decision. Force-unlocking while another apply is active can cause state corruption or conflicting infrastructure changes.

A sound collaborative workflow generally follows this sequence: change configuration in a branch; validate and plan in an isolated or controlled context; review the plan; acquire the remote lock during the approved apply; record outputs and state-version information; and release the lock only after completion. Production plans should be generated close enough to application that their assumptions remain valid, particularly when infrastructure changes frequently.

Resource Addressing and Identity

Terraform addresses identify objects in a configuration and in state. A simple resource address has the form `aws_instance.web`. A resource using `count` has indexed instances such as `aws_instance.web[0]`, while a resource using `for_each` has key-based instances such as `aws_instance.web["blue"]`. Module paths are prefixed when a resource belongs to a module, for example `module.network.aws_subnet.private["a"]`.

Addressing matters because Terraform tracks identity through these addresses. Changing an address can make an existing object appear absent and a new object appear necessary, even when the real infrastructure object has not changed. This is especially common when a resource is renamed, moved into a module, or changed from an unkeyed collection to a keyed collection.

`count` and `for_each` have different identity consequences. Count-based indexes can shift when list ordering changes. For example, removing the first item can cause later indexes to represent different logical objects. `for_each` keys are generally more stable when they reflect meaningful identities, such as availability-zone names or service identifiers. The choice is therefore not merely syntactic; it influences long-term lifecycle safety.

Terraform’s dependency graph is built from references and explicit dependency declarations. If a database subnet references a network resource, Terraform can infer that the network must exist first. `depends_on` is appropriate when a real dependency is not visible through an attribute reference, but unnecessary explicit dependencies can make the graph conservative and reduce parallelism. The graph determines ordering, not business approval, and it cannot automatically understand every external dependency.

Lifecycle Actions: Create, Update, Destroy, and Replace

Terraform plans typically describe actions as creation, in-place update, destruction, or replacement. A replacement occurs when a changed attribute cannot be modified by the provider in place, when an immutable property changes, or when lifecycle rules require recreation. Plans may show replacement symbols or a destroy-and-create sequence. Replacement is often more disruptive than an update because it can change identifiers, addresses, IPs, attached data, or service availability.

The `lifecycle` block provides controls such as `create_before_destroy`, `prevent_destroy`, and `ignore_changes`. `create_before_destroy` can reduce downtime when the provider and naming constraints permit two objects to coexist. It can also fail when a unique name, quota, or exclusive attachment prevents parallel existence. `prevent_destroy` is a safety guard for critical resources, but it can block legitimate migrations and should not replace backup and recovery planning. `ignore_changes` can accommodate an intentional external controller, yet excessive use hides drift and weakens Terraform’s authority over the resource.

Replacement risk must be assessed from the actual plan, provider behavior, data sensitivity, and dependency graph. A small configuration edit can cause a large operational effect if it forces replacement of a foundational network, encryption key, database, or identity object. Plan review should therefore ask not only “Does the plan match the ticket?” but also “Which downstream resources depend on this object, and what happens if its identity changes?”

Importing Existing Infrastructure

Import is used when infrastructure already exists but is not represented in Terraform state. A resource block describing the object is created, and an import operation associates the provider-side object with a Terraform address. Import does not automatically produce a complete, correct configuration. After importing, the configuration must be reconciled with the object’s actual attributes and provider defaults.

A reliable import process begins with inventory and ownership verification. The operator identifies the exact provider identifier, confirms that the object should become Terraform-managed, defines its intended address, and writes a resource block. After import, the operator runs a plan and examines differences. Attributes that are required or semantically important must be represented explicitly; computed values should not be copied blindly. The goal is a stable configuration that produces no unintended changes.

Import is particularly useful during modernization, merger of manually created environments, and adoption of infrastructure-as-code by an existing operations team. It is also a governance event: once imported, the team must decide whether Terraform is authoritative and how future manual changes will be handled.

Moving Resources Safely

A resource move changes its Terraform address while preserving its relationship with the real object. This is required when a resource is renamed, moved into a module, or reorganized within a configuration. A declarative `moved` block records the old and new addresses, allowing Terraform to interpret the change as a move rather than a destroy-and-create operation.

For example:

```hcl

moved {

from = aws_instance.web

to = module.compute.aws_instance.web

}

```

The exact addresses must correspond to the prior and new configuration, including indexes or keys where applicable. A move should be tested with a plan that shows the intended address transition and no replacement. Teams should preserve move declarations long enough to support all relevant state transitions and remove them only under a deliberate state-history policy. Direct state manipulation commands can also move addresses, but declarative move statements are usually easier to review and reproduce.

Moving is not importing. Import associates an untracked external object with an address; moving changes the address of an object that Terraform already tracks. Confusing these operations can produce duplicate management, accidental recreation, or an orphaned state entry.

Drift and Reconciliation

Drift is a difference between Terraform’s desired configuration or recorded assumptions and the actual external infrastructure. It may result from a console change, an emergency command, an external controller, provider behavior, or an incomplete previous operation. A refresh-aware plan can reveal many forms of drift, but not every difference is detectable or meaningful. Some provider attributes are computed, normalized, eventually consistent, or unavailable through the API.

The correct response to drift depends on intent. If the external change is unauthorized, applying Terraform may restore the declared configuration. If it is legitimate, the team should update configuration and state through a reviewed change. If another system is intentionally authoritative for an attribute, a carefully scoped `ignore_changes` rule may be appropriate. Silently ignoring broad classes of drift creates a false sense of control.

Drift detection is both technical and organizational. Regular plans, cloud audit logs, policy controls, and restricted console access complement Terraform. A team should define who may make emergency changes, how those changes are recorded, and how temporary divergence is reconciled.

State Security and Governance

State security requires protection of confidentiality, integrity, and availability. Confidentiality controls include encryption at rest and in transit, restrictive identity policies, private storage endpoints where appropriate, and avoidance of broad read access. Integrity controls include versioning, access logs, controlled write permissions, immutable or protected backups, and separation of duties. Availability controls include replicated storage, tested recovery procedures, and documented backend dependencies.

State should be treated according to the most sensitive value it may contain. Masking a value in command output does not necessarily remove it from state. State should not be committed to a public repository, copied into tickets, pasted into chat, or exposed through diagnostic archives without review. Backend credentials, cloud credentials, and CI secrets should be supplied through secure identity mechanisms rather than hard-coded configuration.

Organizations should establish ownership for each state location, define retention and deletion policies, restrict production applies, and record state-changing operations. Regulatory requirements may affect data residency, retention, encryption-key management, and access review. The technical ability to read state should be granted only to roles that need it, because state access can be equivalent to access to sensitive infrastructure information.

State Recovery and Failure Handling

Recovery begins with diagnosis. Operators should determine whether the problem is a stale lock, a failed provider operation, a missing object, an incorrect address, a corrupted state version, or ordinary drift. They should preserve logs, identify the last known good state version, and avoid repeated applies that may compound uncertainty.

Remote backends with versioning make recovery substantially safer. A prior state version can be selected for restoration according to backend capabilities and organizational procedure. Restoration must be followed by a plan and careful comparison with real infrastructure; rolling state backward does not automatically roll infrastructure backward. If the state says an object exists when it does not, a refresh or targeted corrective operation may be required. If an object exists but is absent from state, importing it may restore management.

State surgery should be exceptional. Commands that remove or alter state entries can be useful when an object has been intentionally transferred to another state or when an address is irreparably wrong, but they do not change provider-side infrastructure. Every such operation should be reviewed, backed up, logged, and followed by a plan. In a severe incident, the safest approach may involve temporarily stopping automation, making the backend read-only if possible, reconstructing ownership, and involving platform and security specialists.

An Illustrative Case: Migrating a Shared Network State

Background

Northstar Digital, a fictional software company, managed its cloud network from a local state file maintained by one platform engineer. The network supported development and production workloads. Application teams had recently begun using CI/CD pipelines, and two pipelines were configured to apply changes against the same environment.

Situation and Problem

An engineer moved a subnet resource into a reusable module and changed a collection from `count` to `for_each`. A subsequent plan proposed destruction and recreation of several subnets. At nearly the same time, a second pipeline attempted an apply using an older copy of the local state. The team also discovered that a security-group rule had been modified manually during an incident.

Analysis

Three distinct problems were interacting. First, the collection change altered resource addresses; Terraform could not infer that the old indexed objects were the same logical objects as the new keyed instances. Second, local state provided no reliable concurrency control. Third, manual modification created drift, and the team had no documented decision about whether Terraform or the emergency operator was authoritative.

Approach and Solution

The team stopped both pipelines and backed up the local state files and configuration. It inventoried the actual subnets and identified the intended stable keys. Declarative `moved` blocks were added for address transitions where correspondence was certain. Objects whose correspondence could not be safely established were reviewed individually rather than forced through a bulk operation.

The team then configured a remote backend with encryption, restricted access, versioning, and state locking. A single controlled plan was generated and reviewed by the network owner. The manually changed security rule was compared with the approved emergency record; because the change was legitimate, configuration was updated and the rule was brought under normal review. The final plan showed address moves and the intended rule update, but no subnet replacement. Only after this evidence was accepted did the team apply the plan.

Outcome

The network remained available, the state became accessible to authorized automation, and concurrent applies were prevented. The organization also introduced separate state boundaries for shared networking and application infrastructure, mandatory plan review for production, and a recovery runbook covering stale locks and state-version restoration.

Learning Points

The case demonstrates that a lifecycle incident is rarely caused by one syntax error. Resource identity, state concurrency, collection design, drift policy, and organizational workflow interact. Stable addresses should be designed before large-scale adoption, remote state should be introduced before collaboration becomes critical, and every replacement plan should be assessed in terms of dependency and service impact.

Common Errors and Professional Trade-offs

A frequent mistake is storing state in a source-control repository because configuration is stored there. Version control is valuable for configuration, but it is not automatically an appropriate state backend or secret-management system. Another mistake is assuming that a successful plan proves safety. Plans depend on provider observations, credentials, timing, and the correctness of state; they require interpretation.

Teams also overuse `-target` to force a partial apply. Targeting can be useful during carefully controlled recovery, but routine use may leave dependent resources inconsistent and conceal the actual graph. Similarly, broad `ignore_changes` rules may silence plans while allowing unauthorized or accidental divergence. Manual editing of state is risky because the serialized structure contains relationships and metadata that are easy to damage.

There is a trade-off between centralized state and state fragmentation. A single state can simplify references but increases blast radius, lock contention, and access scope. Multiple states reduce those risks but require explicit interfaces, such as remote outputs, and careful coordination. The right design follows ownership and lifecycle boundaries rather than an arbitrary preference for either one state or many.

Emerging Practice in State Management

Modern Terraform operations increasingly treat state as a governed platform service rather than a file. Remote execution, policy-as-code, automated plan analysis, signed artifacts, and identity federation reduce dependence on individual workstations. Platform teams are also adopting ephemeral environments, which demand automated creation and destruction while preserving separation from production state.

Provider ecosystems and external controllers create new coordination challenges. Kubernetes operators, cloud-native controllers, and security automation may change objects independently of Terraform. Successful designs explicitly allocate authority: Terraform may own the resource, another controller may own selected fields, or a shared ownership protocol may be required. Automated drift detection and policy evaluation can identify conflicts earlier, but they cannot replace clear ownership.

Artificial intelligence can assist with plan summarization, anomaly detection, and identification of potentially destructive changes. It should not be granted unsupervised authority to approve state changes. Explanations generated by analytical tools must be checked against the actual plan, provider documentation, dependencies, and organizational policy.

Best-Practice Framework

A professional state-management design should include the following practices:

  • Use remote state for shared or production infrastructure, with encryption, versioning, access control, and locking.
  • Separate state according to ownership, sensitivity, lifecycle, and blast radius.
  • Prefer stable `for_each` keys for long-lived collections when meaningful identities exist.
  • Review plans for replacement, dependency impact, data loss, and address changes.
  • Use import for existing unmanaged objects and move operations for tracked address changes.
  • Treat force-unlock, state removal, and state restoration as documented recovery actions.
  • Maintain tested backups and verify that restoring state does not imply restoring infrastructure.
  • Define an explicit policy for console changes, emergency changes, and drift reconciliation.
  • Keep production applies within an auditable workflow using least-privilege identities.
  • Make resource ownership and lifecycle assumptions visible to application, security, and operations teams.

Chapter Summary

Terraform state is the record that connects declarative configuration with provider-managed infrastructure. It enables planning, but it is not the infrastructure itself and should not be treated as an ordinary file. Local state is useful for isolated work, whereas collaborative environments generally require a remote backend with encryption, access control, versioning, and locking. Resource addresses define identity; careless changes to indexes, keys, names, or module paths can cause replacement. Import associates existing infrastructure with state, while move operations preserve identity across address changes. Lifecycle rules influence creation, update, destruction, and replacement, but they require analysis of provider constraints and dependency graphs. Drift must be investigated as both a technical and governance issue. Secure state management includes least privilege, confidential handling, recovery procedures, controlled automation, and explicit ownership. The central professional principle is to make state, identity, concurrency, and lifecycle decisions deliberate before infrastructure changes are applied.

Key Terms

State: Terraform’s recorded representation of managed resource instances and their provider relationships.

Backend: The mechanism and location Terraform uses to store and access state.

Remote state: State stored in a shared service or remote storage system rather than only on a local workstation.

State locking: Coordination that prevents concurrent operations from writing the same state.

Resource address: The path Terraform uses to identify a resource or resource instance.

Resource instance: A particular object created from a resource, often represented through an index or key.

Drift: A difference between declared or recorded infrastructure and the actual external environment.

Import: The operation that associates an existing provider object with a Terraform address in state.

Move: A state-aware address transition that preserves the identity of an already managed object.

Dependency graph: Terraform’s representation of ordering relationships among resources and modules.

Replacement: Destruction and recreation of a resource instead of an in-place update.

In-place update: Modification of an existing resource without destroying its provider-side identity.

Lifecycle block: Terraform configuration controlling selected creation, update, and destruction behavior.

`create_before_destroy`: A lifecycle setting that requests creation of a replacement before destruction of the old object when possible.

`prevent_destroy`: A lifecycle setting that blocks planned destruction of a resource.

`ignore_changes`: A lifecycle setting that prevents selected attribute differences from producing changes.

State version: A stored historical revision of state maintained by a backend or recovery system.

Force-unlock: An administrative operation that removes a lock believed to be stale.

Blast radius: The extent of infrastructure and service impact resulting from a change or failure.

Remote execution: Running Terraform planning or applying within a controlled service rather than an individual workstation.

Review Questions

Short Answer Questions

1. Why is Terraform state more than a cache of provider data?

2. What problem does state locking address?

3. Distinguish importing a resource from moving a resource.

4. How can `for_each` provide more stable identity than `count`?

5. Why does restoring an earlier state version not automatically restore infrastructure?

Long Answer Questions

1. Explain the relationship among configuration, state, provider observations, planning, and application in Terraform’s reconciliation model.

2. Compare local and remote state with respect to collaboration, security, concurrency, auditability, and recovery.

3. Discuss how resource addresses and dependency graphs influence Terraform lifecycle behavior.

4. Explain the causes of drift and evaluate alternative responses to intentional and unintentional drift.

5. Design a state-management policy for a production organization, including backend security, locking, access control, recovery, and review.

Analytical / Application Questions

1. A team changes `aws_subnet.private[0]` to `module.network.aws_subnet.private["zone-a"]`, and the plan proposes destruction and recreation. What evidence and Terraform mechanisms should the team use to preserve the existing subnet?

2. Two CI pipelines begin applying changes to one local state file, and the second pipeline overwrites the first pipeline’s state update. Diagnose the failure and propose a technically and organizationally complete remedy.

3. A database resource shows replacement after a minor configuration change. How would you analyze the provider behavior, lifecycle settings, dependency graph, data-protection requirements, and approval process before allowing the apply?

22. Multiple Choice Questions with Answers

Question 1

What is the primary operational purpose of Terraform state?

A. To store provider installation binaries

B. To connect configuration with managed infrastructure and support planning

C. To replace cloud-provider authentication

D. To serve as a source-code repository

Correct Answer: B. To connect configuration with managed infrastructure and support planning

Question 2

Which capability most directly prevents two Terraform applies from writing the same remote state concurrently?

A. State locking

B. Resource import

C. The ignore_changes argument

D. A moved block

Correct Answer: A. State locking

Question 3

A resource changes from aws_instance.web to module.compute.aws_instance.web, but the real instance should remain unchanged. Which approach is most appropriate?

A. Delete the instance manually

B. Use a moved declaration for the address transition

C. Use prevent_destroy on an unrelated resource

D. Create a second instance and ignore the first

Correct Answer: B. Use a moved declaration for the address transition

Question 4

What is a key risk of changing a count-based collection when list ordering changes?

A. Terraform loses all provider credentials

B. Resource indexes may refer to different logical objects

C. Remote backends stop supporting encryption

D. The dependency graph is deleted permanently

Correct Answer: B. Resource indexes may refer to different logical objects

Question 5

What does importing an existing cloud resource accomplish?

A. It automatically makes the configuration complete and drift-free

B. It associates the existing object with a Terraform state address

C. It destroys the existing object and recreates it

D. It converts local state into remote state

Correct Answer: B. It associates the existing object with a Terraform state address

Question 6

Why is force-unlocking considered a recovery operation?

A. It always destroys resources protected by the lock

B. It may permit concurrent operations if another process is still running

C. It permanently disables all future locking

D. It changes every resource address

Correct Answer: B. It may permit concurrent operations if another process is still running

Question 7

A manually changed security-group rule differs from configuration. If the manual change was authorized and should remain, what is the most appropriate general response?

A. Ignore all future changes to the security group

B. Update configuration through review and reconcile Terraform management

C. Delete the state file immediately

D. Force-unlock the backend

Correct Answer: B. Update configuration through review and reconcile Terraform management

Question 8

Which statement about restoring an earlier state version is correct?

A. It automatically rolls the provider infrastructure back

B. It changes Terraform’s recorded model and must be followed by a careful plan

C. It removes the need for provider credentials

D. It always causes every resource to be replaced

Correct Answer: B. It changes Terraform’s recorded model and must be followed by a careful plan

Question 9

Which lifecycle setting can prevent Terraform from planning destruction of a critical resource, but may also block legitimate changes?

A. prevent_destroy

B. for_each

C. depends_on

D. import

Correct Answer: A. prevent_destroy

Question 10

Why should Terraform state receive strong access controls?

A. State can contain sensitive values and infrastructure identifiers

B. State is always larger than the configuration

C. State cannot be versioned remotely

D. State contains only public documentation

Correct Answer: A. State can contain sensitive values and infrastructure identifiers

Infrastructure as Code (IaC) with Terraform/Ansible
Chapter 4 of 14