A local terraform.tfstate on a laptop does not scale: the moment two teammates touch the same infrastructure, the state drifts apart, a parallel terraform apply corrupts the file, and nobody holds the current source of truth. Remote state fixes that — one shared, central state plus locking that blocks a second run instead of corrupting the state. The catch: in most tutorials that sensitive state ends up in an AWS S3 bucket in the US. intercolo i3 is the S3-compatible backend in Germany: strong read-after-write, reached via the endpoint https://de-fra.i3storage.com in region de-fra, with native locking via use_lockfile (Terraform 1.10+ / OpenTofu 1.8+) — no DynamoDB, GDPR/DPA, 99.999999% durability, and data that never leaves the Frankfurt facility.
endpoints = { s3 = ... } points the backend at de-fra.i3storage.com instead of AWS. The three skip_ flags are mandatory against a non-AWS endpoint: skip_credentials_validation skips the AWS STS check, skip_requesting_account_id the IAM account lookup, skip_region_validation the check against the AWS region list (de-fra is not an AWS region). use_lockfile = true enables native locking. Credentials come from the environment variables AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (your SigV4 key pair).
Classically the AWS S3 backend needed a separate DynamoDB table (dynamodb_table) as the lock — without it, two concurrent runs could destroy the state. Since Terraform 1.10 and OpenTofu 1.8 there is use_lockfile = true: before any writing run the backend creates a .tflock object right next to the state file — via conditional S3 writes (If-None-Match) that let exactly one writer win. No second service, no extra bill, no additional IAM policy for DynamoDB. intercolo i3 supports these conditional writes, so the lock works cleanly for your whole team. One backend block, done — the second apply waits or fails with a lock error instead of overwriting the state.
Create a bucket (e.g. tf-state) — via the web console, aws-cli or rclone against https://de-fra.i3storage.com. Generate a SigV4 API key pair (access/secret). For clean separation use a dedicated key pair and ideally a dedicated bucket per environment (dev/stage/prod).
Add the terraform { backend "s3" { ... } } block from the example: bucket, key, region = de-fra, endpoints = { s3 = "https://de-fra.i3storage.com" }, use_lockfile = true and the three skip_ flags. Export AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY with your SigV4 keys (do not commit them to code).
Run terraform init -migrate-state (for new projects plain terraform init is enough). Terraform copies your existing local terraform.tfstate into the bucket and confirms the migration. Afterwards delete or securely archive the local state file — from now on only the remote copy is authoritative.
From now on every terraform plan / apply acquires the .tflock lock before writing and releases it afterwards. A second, concurrently running apply blocks with a lock error instead of damaging the state. Verify with terraform plan that no changes are pending — then your state sits cleanly in Frankfurt.
Honest and important: a terraform.tfstate can hold plaintext values — DB passwords, API tokens, generated keys. There is NO storage-side at-rest encryption, only TLS in transit. So keep secrets out of the state where you can: reference them through a secret manager or HashiCorp Vault (data sources instead of plaintext resources) rather than writing them into resources that land in state. Where a provider must persist a value, encrypt the state client-side before it reaches the backend — OpenTofu 1.7+ can encrypt state and plan natively on the client, and with Terraform a SOPS/age workflow in your pipeline does the job. Access runs exclusively through SigV4 API keys over TLS — no public bucket. Use separate keys and buckets per environment so a leaked key has a small blast radius.
| AWS S3 + DynamoDB | Wasabi | intercolo i3 | |
|---|---|---|---|
| Location / data residency | US cloud, CLOUD Act | EU region selectable, US corporation | Frankfurt DC, 100% DE, GDPR/DPA |
| Endpoint | s3.<region>.amazonaws.com | s3.<region>.wasabisys.com | https://de-fra.i3storage.com (de-fra) |
| State locking | DynamoDB (classic) or use_lockfile | depends on conditional-write support | native use_lockfile (conditional writes) |
| Extra service required | DynamoDB table (classic) | none | none |
| Egress model | per GB, plus request fees | 'free' up to storage limit, then throttled | 1 TB incl./TB, then +€3.99/TB |
| Encryption at rest | SSE-S3 / KMS available | SSE available | No — client-side (OpenTofu/SOPS), TLS transport |
| Support / contact | Ticket, EN | Ticket, EN | Direct, German, DC team |
If you already use the AWS S3 backend, migration is a minimal diff: in your existing backend "s3" block set region = "de-fra", add endpoints = { s3 = "https://de-fra.i3storage.com" } plus the three skip_ flags and use_lockfile = true — and remove the old dynamodb_table line. Point AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY at your intercolo SigV4 keys. A terraform init -migrate-state copies the state from AWS into the Frankfurt bucket. Your resources, modules and providers stay untouched — only the state moves house. To confirm, terraform plan then shows no changes.
Works with HashiCorp Terraform 1.10+ (use_lockfile) and OpenTofu 1.8+ — the same backend block for both. OpenTofu additionally ships native, client-side state and plan encryption (from 1.7) if you want to store the state encrypted.
In GitLab CI, GitHub Actions, Atlantis or Jenkins, set AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (SigV4) and region = de-fra as protected variables. The native lock serialises concurrent pipeline runs — two merge requests cannot apply at the same time.
Separate dev/stage/prod via Terraform workspaces or dedicated key prefixes (env:/prod/...) — in one or several buckets. Combined with a dedicated SigV4 key pair per environment, every state stays isolated organisationally and by key.
After apply the next read returns the current state immediately — strong read-after-write consistency. No stale state files, no races between back-to-back runs of the kind that eventually-consistent stores provoke.
Naše skúsenosti hovoria za seba: Úspešne sme dokončili migrácie zahŕňajúce viac petabajtov dát a viac ako 500 miliónov súborov.