---
title: "Keep state remote with locking and · Agent Wranglers"
url: https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code/remote-state
updated: 2026-10-20
licence: "All rights reserved"
---

# Keep state remote with locking and encryption

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Infrastructure as code](https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code)
- Rule: Keep the state file in a remote backend that locks, encrypts and keeps old versions. Only the deploy roles may read it. Never keep state in git or on a laptop.
- How: The state file holds every attribute of every resource in plain text, sometimes with passwords. Store it in a bucket with versioning, encryption and public access blocked. In the S3 backend, set `use_lockfile = true`. It needs Terraform 1.11 or OpenTofu 1.10 or later, and the older DynamoDB lock table is deprecated. OpenTofu can also encrypt state and plan files on the client with its `encryption` block and a key from a key service. Set `enforced = true`, so an unencrypted file is refused. Pulumi encrypts secret values in state with a secrets provider.
- Check: `git ls-files '*.tfstate*'` prints nothing. Start two plans on the same state at once, and the second stops with a lock error. The bucket reports versioning `Enabled` and public access blocked. Its policy grants read access to the deploy roles only.

Example:

```hcl
terraform {
  backend "s3" {
    bucket       = "example-shop-state"
    key          = "production/network.tfstate"
    region       = "eu-north-1"
    encrypt      = true
    use_lockfile = true
  }
}
```

```hcl
# OpenTofu only: state and plan files are encrypted before they leave the runner
terraform {
  encryption {
    key_provider "aws_kms" "state" {
      kms_key_id = "alias/example-shop-state"
      region     = "eu-north-1"
      key_spec   = "AES_256"
    }
    method "aes_gcm" "state" {
      keys = key_provider.aws_kms.state
    }
    state {
      method   = method.aes_gcm.state
      enforced = true
    }
    plan {
      method   = method.aes_gcm.state
      enforced = true
    }
  }
}
```

```sh
aws s3api get-bucket-versioning --bucket example-shop-state           # "Status": "Enabled"
aws s3api get-public-access-block --bucket example-shop-state         # all four settings true
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers infrastructure playbook: https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code/remote-state`.

- Previous: [Define every resource in code](https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code/define-infrastructure-in-code)
- Next: [Review the plan and apply only from CI](https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code/plan-in-review)
- [Infrastructure as code](https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code)
