Skip to content

Enables S3 native state locking on the Terraform backend#20

Merged
mcrundo merged 1 commit into
mainfrom
enable-native-state-locking
Apr 17, 2026
Merged

Enables S3 native state locking on the Terraform backend#20
mcrundo merged 1 commit into
mainfrom
enable-native-state-locking

Conversation

@mcrundo
Copy link
Copy Markdown
Owner

@mcrundo mcrundo commented Apr 17, 2026

Summary

Enables S3 native state locking on the Terraform backend to prevent concurrent terraform apply runs
(e.g. a local apply racing a GitHub Actions apply) from corrupting state.

Closes LAD-283.

Changes

  • terraform/main.tf — Added use_lockfile = true to the S3 backend block. Bumped
    required_version from >= 1.5 to >= 1.10 since use_lockfile is a Terraform 1.10 feature.
  • .github/workflows/ci.yml + deploy.yml — Bumped TERRAFORM_VERSION from "1.5" to "1.10" to
    match the new minimum.

Why S3 native locking over DynamoDB?

Terraform 1.10 added lockfile-based locking for the S3 backend, which keeps the lock in the same bucket
as the state. This removes the need for a separate aws_dynamodb_table resource and its associated IAM
permissions — one less thing to provision and pay for.

Operator steps after merge

Anyone with a local Terraform install needs to upgrade to 1.10+ — the Homebrew core terraform formula
is frozen at 1.5.7 due to the HashiCorp BSL license change, so use the official tap or tfenv:

# via HashiCorp's tap
brew uninstall terraform
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# or via tfenv
brew install tfenv
tfenv install 1.10.5
tfenv use 1.10.5

Then run terraform init -reconfigure in terraform/ to migrate the backend.

Test plan

- terraform init -reconfigure succeeds on 1.10+
- terraform plan shows no infrastructure changes (backend-only change)
- CI terraform-validate and terraform-plan jobs pass with the new version
- Confirm the first post-merge apply creates dev/terraform.tfstate.tflock in the state bucket during the
 run and releases it on completion

@github-actions
Copy link
Copy Markdown

Terraform Plan 📖success

Show Plan
aws_secretsmanager_secret.google_maps_api_key: Refreshing state... [id=arn:aws:secretsmanager:us-east-2:023179631616:secret:address-validation/dev/google-maps-api-key]
aws_sqs_queue.handler_dlq: Refreshing state... [id=https://sqs.us-east-2.amazonaws.com/023179631616/address-validation-handler-dlq-dev]
aws_cloudwatch_log_group.health: Refreshing state... [id=/aws/lambda/address-validation-health-dev]
aws_cloudwatch_log_group.api_gateway: Refreshing state... [id=/aws/apigateway/address-validation-dev]
aws_cloudwatch_log_group.authorizer: Refreshing state... [id=/aws/lambda/address-validation-authorizer-dev]
aws_cloudwatch_log_group.handler: Refreshing state... [id=/aws/lambda/address-validation-handler-dev]
data.aws_iam_policy_document.lambda_assume_role: Reading...
aws_secretsmanager_secret.api_key: Refreshing state... [id=arn:aws:secretsmanager:us-east-2:023179631616:secret:address-validation/dev/api-key]
aws_apigatewayv2_api.this: Refreshing state... [id=py1sucuxvi]
data.aws_iam_policy_document.lambda_assume_role: Read complete after 0s [id=2690255455]
aws_iam_role.lambda_exec: Refreshing state... [id=address-validation-lambda-dev]
data.aws_iam_policy_document.secrets_read: Reading...
data.aws_iam_policy_document.secrets_read: Read complete after 0s [id=3213121637]
data.aws_iam_policy_document.authorizer_secrets: Reading...
data.aws_iam_policy_document.authorizer_secrets: Read complete after 0s [id=2856189735]
data.aws_iam_policy_document.dlq_send: Reading...
aws_cloudwatch_metric_alarm.dlq_depth: Refreshing state... [id=address-validation-dlq-depth-dev]
data.aws_iam_policy_document.dlq_send: Read complete after 0s [id=1518684834]
aws_apigatewayv2_stage.default: Refreshing state... [id=$default]
data.aws_iam_policy_document.lambda_logs: Reading...
data.aws_iam_policy_document.lambda_logs: Read complete after 0s [id=543498868]
aws_iam_role_policy.dlq_send: Refreshing state... [id=address-validation-lambda-dev:dlq-send]
aws_lambda_function.handler: Refreshing state... [id=address-validation-handler-dev]
aws_lambda_function.health: Refreshing state... [id=address-validation-health-dev]
aws_iam_role_policy.authorizer_secrets: Refreshing state... [id=address-validation-lambda-dev:authorizer-secrets-read]
aws_iam_role_policy.secrets_read: Refreshing state... [id=address-validation-lambda-dev:secrets-read]
aws_iam_role_policy.lambda_logs: Refreshing state... [id=address-validation-lambda-dev:cloudwatch-logs]
aws_iam_role_policy_attachment.xray: Refreshing state... [id=address-validation-lambda-dev-20260323021610749600000001]
aws_lambda_function.authorizer: Refreshing state... [id=address-validation-authorizer-dev]
aws_lambda_permission.api_gateway_health: Refreshing state... [id=AllowAPIGatewayInvokeHealth]
aws_apigatewayv2_integration.health: Refreshing state... [id=6ktcygu]
aws_lambda_permission.api_gateway_handler: Refreshing state... [id=AllowAPIGatewayInvokeHandler]
aws_cloudwatch_metric_alarm.handler_duration: Refreshing state... [id=address-validation-handler-duration-dev]
aws_apigatewayv2_integration.handler: Refreshing state... [id=s8f1dfh]
aws_cloudwatch_metric_alarm.handler_throttles: Refreshing state... [id=address-validation-handler-throttles-dev]
aws_cloudwatch_metric_alarm.handler_errors: Refreshing state... [id=address-validation-handler-errors-dev]
aws_apigatewayv2_authorizer.api_key: Refreshing state... [id=64l2kl]
aws_lambda_permission.authorizer_invoke: Refreshing state... [id=AllowAPIGatewayInvokeAuthorizer]
aws_cloudwatch_metric_alarm.authorizer_errors: Refreshing state... [id=address-validation-authorizer-errors-dev]
aws_apigatewayv2_route.health: Refreshing state... [id=8s7aw6n]
aws_apigatewayv2_route.validate: Refreshing state... [id=n68uzib]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_lambda_function.authorizer will be updated in-place
  ~ resource "aws_lambda_function" "authorizer" {
        id                             = "address-validation-authorizer-dev"
      ~ last_modified                  = "2026-04-16T04:56:37.000+0000" -> (known after apply)
      ~ source_code_hash               = "ojDsXSGWm0LgDq7H7nJIewOYS6FjUtNr6sTmTXFZIbk=" -> "/R8metDMKn9abNHtkh3AS6RMWq4xkfd3C2LtWu1vQFI="
        tags                           = {}
        # (27 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

  # aws_lambda_function.handler will be updated in-place
  ~ resource "aws_lambda_function" "handler" {
        id                             = "address-validation-handler-dev"
      ~ last_modified                  = "2026-04-16T04:56:31.000+0000" -> (known after apply)
      ~ source_code_hash               = "ojDsXSGWm0LgDq7H7nJIewOYS6FjUtNr6sTmTXFZIbk=" -> "/R8metDMKn9abNHtkh3AS6RMWq4xkfd3C2LtWu1vQFI="
        tags                           = {}
        # (27 unchanged attributes hidden)

        # (5 unchanged blocks hidden)
    }

  # aws_lambda_function.health will be updated in-place
  ~ resource "aws_lambda_function" "health" {
        id                             = "address-validation-health-dev"
      ~ last_modified                  = "2026-04-16T04:56:25.000+0000" -> (known after apply)
      ~ source_code_hash               = "ojDsXSGWm0LgDq7H7nJIewOYS6FjUtNr6sTmTXFZIbk=" -> "/R8metDMKn9abNHtkh3AS6RMWq4xkfd3C2LtWu1vQFI="
        tags                           = {}
        # (27 unchanged attributes hidden)

        # (3 unchanged blocks hidden)
    }

Plan: 0 to add, 3 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

Triggered by @mcrundo in commit 91bd9e4

@mcrundo mcrundo merged commit b7bf477 into main Apr 17, 2026
4 checks passed
@mcrundo mcrundo deleted the enable-native-state-locking branch April 17, 2026 03:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant