{"id":637,"date":"2025-03-25T04:23:18","date_gmt":"2025-03-25T04:23:18","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=637"},"modified":"2025-03-25T04:23:18","modified_gmt":"2025-03-25T04:23:18","slug":"terraform-state-locking-with-s3","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=637","title":{"rendered":"Terraform State Locking with S3 Without DynamoDB"},"content":{"rendered":"<p>In this blog we will look at,<\/p>\n<ol>\n<li>Need for Terraform State Locking<\/li>\n<li>State Locking With DynamoDB<\/li>\n<li>State Locking with S3 without DynamoDB<\/li>\n<li>When to use DynamoDB and when to use s3 state locking.<\/li>\n<\/ol>\n<p>Before we get into the details, let&#8217;s understand some basics.<\/p>\n<p>So what is state lock?<\/p>\n<p>When we look at real-world Terraform implementations, most Terraform infrastructure deployments happen through CI\/CD systems.<\/p>\n<p>This means that, at any given point in time, the same Terraform state could be accessed by different CI\/CD jobs. Also, multiple developers could access the same state file during the development process.<\/p>\n<p>If multiple terraform process uses the same state file, it could lead to conflicts and&nbsp;<strong>inconsistencies in the state file<\/strong>. (race conditions).<\/p>\n<p>So we need state locking to ensure one terraform process modifies the resource at a time.<\/p>\n<p>It is like putting a &#8220;<strong>do not disturb<\/strong>&#8221; sign on your state file.<\/p>\n<p>Organisations usually store the Terraform state file in an Amazon S3 bucket. This provides a central place that&#8217;s durable and accessible for the team<\/p>\n<p>Along with S3, a DynamoDB table is set up to manage locks.<\/p>\n<h2 id=\"state-locking-with-dynamodb\">State Locking With DynamoDB<\/h2>\n<p>In AWS environment, the standard approach is <a href=\"https:\/\/devopscube.com\/setup-terraform-remote-state-s3-dynamodb\/\" rel=\"noreferrer\">using DynamoDB for state locking<\/a>.<\/p>\n<p>Here is how&nbsp;<strong>DynamoDB<\/strong>&nbsp;state locking works.<\/p>\n<ol>\n<li>When Terraform wants to modify a resource, it acquires a lock in&nbsp;<strong>DynamoDB<\/strong>&nbsp;by creating an entry in&nbsp;<strong>DynamoDB<\/strong>&nbsp;table with a specific lock ID (e.g., \u201clock-abc123\u201d).<\/li>\n<li>If the lock is successful, terraform gets access to the state file from s3<\/li>\n<li>Once all the resource modifications are done, Terraform updates the state file and releases the&nbsp;<strong>DynamoDB<\/strong>&nbsp;lock.<\/li>\n<\/ol>\n<p>For example, when&nbsp;<strong>developer X<\/strong>&nbsp;executes the terraform code, DynamoDB will lock the state, and&nbsp;<strong>developer Y<\/strong>&nbsp;should wait until the execution is completed.<\/p>\n<p>Also, DynamoDB has a timeout period to&nbsp;<strong>prevent permanent lock-outs<\/strong>. This is helpful in cases where a lock is acquired by terraform, and it holds the lock due to abnormal process termination.<\/p>\n<p>The following image shows the Terraform s3 backend workflow with DynamoDB locking feature.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/terraform-state-locking-1.gif\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"780\" height=\"882\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/terraform-state-locking-1.gif 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/terraform-state-locking-1.gif 780w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h2 id=\"state-locking-with-s3-lockfile-experimental-feature\">State Locking With s3 Lockfile ( Experimental Feature)<\/h2>\n<p>With S3 now supporting&nbsp;<a href=\"https:\/\/aws.amazon.com\/about-aws\/whats-new\/2024\/08\/amazon-s3-conditional-writes\/?ref=devopscube.com\" rel=\"noreferrer\"><strong>conditional writes<\/strong><\/a>, it can handle concurrency and state locking.<\/p>\n<blockquote><p>Conditional writes allow S3 to prevent overwrites unless certain conditions are met. This ensures stronger consistency when multiple processes attempt to update the same S3 object.<\/p><\/blockquote>\n<p>For example:<\/p>\n<blockquote><p>&#8211; You can update an object only if it has not changed since the last read.<\/p><\/blockquote>\n<blockquote><p>&#8211; This prevents race conditions where two Terraform processes might overwrite the same state file.<\/p><\/blockquote>\n<p>The Terraform backend configuration that uses DynamoDB for state looks like this:<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-337.png\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"592\" height=\"312\"><\/figure>\n<p>You can replace DynamoDB with an s3 lock using&nbsp;<code>use_lockfile<\/code>&nbsp;a flag as shown below. (introduced as experimental in&nbsp;<a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/upgrade-guides?ref=devopscube.com\" rel=\"noreferrer\">Terraform 1.10<\/a>)<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-338.png\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"579\" height=\"312\"><\/figure>\n<p>You can copy and add the below backend block in your Terraform code to use the S3 lock.<\/p>\n<pre><code class=\"language-groovy\">terraform {\n  backend \"s3\" {\n    bucket = \"my-terraform-state-bucket\"\n    key    = \"terraform\/state.tfstate\"\n    region = \"us-west-1\"\n    use_lockfile = true\n   }\n}<\/code><\/pre>\n<p>In the above block, update your bucket name, key structure, and region.<\/p>\n<p>Here is how it works.<\/p>\n<p>Terraform creates a&nbsp;<code>.tflock<\/code>&nbsp;file in S3 before modifying the state to prevent conflicts.<\/p>\n<p>It checks for an existing lock and waits or fails if another process is running.&nbsp;<strong>S3\u2019s conditional writes<\/strong>&nbsp;enforce locking.<\/p>\n<p>When you apply the Terraform script, you can see a&nbsp;<code>.tflock file<\/code>&nbsp;in S3, as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-339.png\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"989\" height=\"407\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-339.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-339.png 989w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Once done, Terraform deletes the lock file.<\/p>\n<p>If another user tries to apply, they will get an error similar as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-340.png\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"1146\" height=\"534\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-340.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-340.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-340.png 1146w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h2 id=\"best-practices\">Best Practices<\/h2>\n<p>Following are some of the best practices you can follow while using s3 as remote state storage.<\/p>\n<ol>\n<li><strong>Enable Versioning on the S3 Bucket: <\/strong>Turn on versioning for the S3 bucket. This keeps track of all changes to the state file, allowing you to recover previous versions if something goes wrong<\/li>\n<li><strong>Set Up Proper Access Controls:<\/strong> Use AWS IAM policies to restrict who can read or write to the S3 bucket and DynamoDB table. This ensures that only authorized team members can make changes.<\/li>\n<li><strong>Encrypt the State File:<\/strong> Enable encryption for the state file stored in S3. This protects sensitive information from unauthorized access.<\/li>\n<li><strong>Handle Locking Issues Carefully: <\/strong>If a Terraform process ends unexpectedly, it might leave a lock in place. Terraform provides a command to manually remove such locks, but use it cautiously to avoid conflicts.\u200b<\/li>\n<li><strong>Organize State Files Clearly:<\/strong> For different projects or environments (like development and production), use separate state files. This reduces the risk of accidental changes affecting the wrong environment.<\/li>\n<\/ol>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>Replacing DynamoDB with&nbsp;<code>use_lockfile<\/code>&nbsp;simplifies your Terraform setup but comes with some trade-offs. Also, it is an experimental feature.<\/p>\n<p>So when should you consider using&nbsp;<code>use_lockfile?<\/code><\/p>\n<ul>\n<li>If you want a&nbsp;<strong>simpler setup<\/strong>&nbsp;without additional AWS resources.<\/li>\n<li>If&nbsp;<strong>cost<\/strong>&nbsp;is a concern and you don\u2019t want to maintain a DynamoDB table.<\/li>\n<li>If your Terraform runs are&nbsp;<strong>infrequent<\/strong>&nbsp;and don\u2019t need high concurrency control. Because S3 locking is&nbsp;<strong>eventually consistent<\/strong>, it could theoretically lead to race conditions in high-concurrency scenarios.<\/li>\n<\/ul>\n<p>When to Stick with DynamoDB<\/p>\n<ul>\n<li>If you have&nbsp;<strong>multiple teams<\/strong>&nbsp;working on the same Terraform state file.<\/li>\n<li>If you need&nbsp;<strong>stronger locking guarantees<\/strong>&nbsp;with high-frequency Terraform operations. Because DynamoDB provides stronger consistency guarantees and better handling of edge cases (like process crashes).<\/li>\n<li>Also, DynamoDB locking is more battle-tested, while S3 native locking is simpler but newer.<\/li>\n<\/ul>\n<p>To use DynamoDB for Terraform Statelock, refer to this <a href=\"https:\/\/devopscube.com\/setup-terraform-remote-state-s3-dynamodb\/\">detailed blog<\/a>.<\/p>\n<p>Share your thoughts in the comments below!<\/p>\n<p>Want to Stay Ahead in DevOps &amp; Cloud? Join Free Newsletter Below.<\/p>\n<p><!--kg-card-begin: html--><br \/>\n <iframe loading=\"lazy\" src=\"https:\/\/embeds.beehiiv.com\/2a495ef4-3de7-4600-8a0d-de5dc968b372\" data-test-id=\"beehiiv-embed\" width=\"100%\" height=\"320\" frameborder=\"0\" scrolling=\"no\" style=\"border-radius: 4px; border: 2px solid #e5e7eb; margin: 0; background-color: transparent;\"><\/iframe><br \/>\n<!--kg-card-end: html--><\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/terraform-state-locking-with-s3\/\" target=\"_blank\" rel=\"noopener noreferrer\">Terraform State Locking with S3 Without DynamoDB \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/terraform-state-locking-with-s3\/<\/p>\n","protected":false},"author":1,"featured_media":638,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-637","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/637","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=637"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/637\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/638"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=637"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=637"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=637"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}