Terraform State on S3 vs a Local File: The Lock a Solo Operator Still Needs
Daniel Park
September 21, 2026
Terraform state is the map of what you think you own in the cloud. Lose the map, and the next apply becomes archaeology: resources that exist but Terraform forgot, resources Terraform wants to create that already exist under a different address, and destroys you did not mean to schedule. Solo operators often keep that map as a local terraform.tfstate file because “I am the only one who runs apply.” That sentence is true until the second laptop, the CI job, or the day you run apply twice by mistake.
The real question is not S3 versus a file on disk for aesthetics. It is whether you have a lock when two applies try to rewrite the same map—and whether the map still exists when the laptop does not.
What local state quietly assumes
A local state file is fine for a throwaway lab and for the first weekend of a side project. It is fast, free, and requires no IAM. You terraform apply, the file updates, git sometimes accidentally ends up with secrets inside it because you committed the wrong path once and swore never again.
Local state assumes three things that stop being true:
- Only one process will ever write it.
- The machine that holds it will still be available when you need to change production at 11 p.m.
- You will never need a teammate, a future-you on another laptop, or a GitHub Action to run the same root.
Break any of those and local state becomes a liability with a friendly filename. Two applies without a lock can both read the same before-state and write incompatible after-states. One of those writes wins on disk. The cloud does not care which story you believe.
There is also a quieter assumption: that you will treat the state file like a secret. Local state often contains resource attributes you would never paste into a ticket. Sitting in ~/projects/app/ next to README.md makes it easy to zip the folder for a friend, sync it through a consumer backup tool, or leave it on a machine you later wipe without copying the one file that mattered.

What remote state on S3 (with a lock) actually buys
Remote state in an S3 bucket is not magic. It is a durable object plus, if you wire it correctly, a lock so only one apply mutates the map at a time. DynamoDB locking with the S3 backend is the classic pair; other backends have their own lock stories. The point is the same: serialize writers.
For a solo operator, the lock still matters. You will eventually run apply from a laptop while a forgotten CI job finishes, or open two terminals because the first one looked stuck. You will also want the state to survive a stolen bag or a dead SSD. S3 versioning turns “I overwrote state with a botched apply” into “I can roll the object back,” which is not the same as a full disaster plan but is better than a single local file you never copied off the machine.
Encryption at rest, tight IAM, and keeping state out of git are table stakes. Remote state does not remove the need for discipline; it removes the fantasy that discipline alone will prevent concurrent writers.
Remote state also makes CI honest. A pipeline that plans on every pull request needs a consistent map. If that map lives only on your laptop, CI invents its own empty world or fails in ways that teach the team nothing. Put state in S3, give the role least privilege on that prefix, and suddenly “what would this PR change?” is a real question instead of a ritual.

The failure mode local state hides
You apply from home. It works. Two weeks later you apply from a café laptop after cloning the repo—but you never copied terraform.tfstate. Terraform plans a full create. You panic, cancel, then invent a terraform import afternoon. Or you do not cancel, and now you have duplicate resources and a bill that explains itself poorly.
Or you did copy the file, but an old copy. The plan looks small. The apply deletes something that only existed in the newer state you left on the other machine. Local state makes that class of error feel like user error. Remote state with a single source of truth makes it harder to apply from a stale map by accident—though you can still force bad plans if you ignore the plan output.
Locks fail closed when configured well: a stuck lock after a killed process is annoying, and that annoyance is the feature. Clearing a lock should be a deliberate act with a reason written down, not muscle memory. Solo operators who skip the lock “because I am careful” eventually clear nothing—they just corrupt state silently when two writers overlap for thirty seconds.
Another local-state failure: partial applies. Terraform crashed mid-way, the local file is half-updated or you restored an older backup of the project folder from Time Machine, and now state and reality disagree in a way that only shows up as a terrifying plan weeks later. Remote versioning does not fix logic bugs, but it gives you a trail of state objects to compare when you are trying to explain how you got here.
When a local file is still acceptable
Keep local state when the root manages nothing you would cry about recreating: a personal sandbox account, ephemeral demo infra, or a module you only ever apply on one workstation with no CI. Even then, copy the state into an encrypted backup once a week if the sandbox starts to hold real DNS or a database you care about.
Do not keep local state for anything that serves customers, holds production DNS, or sits in front of a billing system. “I am solo” is not a lock. It is a scheduling preference that reality will violate.
If you are mid-migration and still on local state for production, treat the next working day as a freeze-and-move window: stop applies, push state to the remote backend, confirm a no-op plan from a second machine, then unlock your normal pace. Half-migrated setups—local for prod, remote for staging—are how people apply the wrong root with the wrong map.
A minimal remote setup that respects a one-person shop
You do not need a platform team. You need:
- A dedicated state bucket with versioning on.
- A lock table (or equivalent) tied to that backend.
- IAM that lets your user and your CI role read/write only that prefix.
- A habit of never committing state or lock files.
- A documented unlock procedure for the day a process dies mid-apply.
Optional but useful: separate state per environment so a broken staging apply cannot contend with production locks. Solo founders often share one state file for everything until the first scary plan. Split earlier than that.
If you already use Terraform Cloud or another managed state service, the same rule applies—remote + lock beats local. S3 is simply the path many people already have credentials for. The brand of the backend matters less than whether two applies can race and whether the map lives off your laptop.
Test the setup with a disposable root first. Create a tiny resource, apply from machine A, plan from machine B, kill an apply to force a lock, practice the unlock, then delete the playground. Doing that drill once costs an hour. Doing it for the first time against production DNS costs a weekend.
Decision rule
Ask: if this laptop vanished tonight, could you still change production tomorrow without guessing which resources exist?
If the answer depends on a file that only lives on that laptop, move state to S3 (or another remote backend) before the next apply. The lock is not bureaucracy for teams of twelve. It is the mutex a solo operator still needs the first time two applies overlap—and the durable map you need the first time hardware fails.
Ask a second question: what happens if apply is interrupted halfway? If your answer is “I will figure it out,” you need versioning and a lock more than you need a prettier module structure.
Local state is a prototype convenience. Remote state with locking is the part where Terraform stops being a script you run and starts being infrastructure you can operate when the week gets messy. Solo does not mean unprotected. It means you are the entire platform team—and platform teams do not keep the map of production in a file that travels only with one backpack.