Terraform · OpenTofu · S3 Backend

Terraform Remote State on an S3 Backend — Frankfurt

Your terraform.tfstate does not belong in the US cloud. Use the German S3-compatible endpoint https://de-fra.i3storage.com (region de-fra) as your backend — with native state locking via use_lockfile, no DynamoDB at all. GDPR/DPA, SigV4, from 2,49 €/TB.

hcl — backend.tf
terraform {
  backend "s3" {
    bucket       = "tf-state"
    key          = "prod/terraform.tfstate"
    region       = "de-fra"
    endpoints    = { s3 = "https://de-fra.i3storage.com" }
    use_lockfile = true   # native S3 state locking, no DynamoDB
    skip_credentials_validation = true
    skip_requesting_account_id  = true
    skip_region_validation      = true
  }
}
S3 Backend
SigV4
Native Locking
without DynamoDB
GDPR / DPA
Frankfurt DC

Remote state with locking — without putting your state in the US cloud

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.

backend.tf — the S3 backend block

hcl
terraform {
  backend "s3" {
    bucket       = "tf-state"
    key          = "prod/terraform.tfstate"
    region       = "de-fra"
    endpoints    = { s3 = "https://de-fra.i3storage.com" }
    use_lockfile = true   # native S3 state locking, no DynamoDB
    skip_credentials_validation = true
    skip_requesting_account_id  = true
    skip_region_validation      = true
  }
}

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).

Native S3 state locking instead of a DynamoDB table

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.

Native S3 state locking instead of a DynamoDB table

Remote state in Frankfurt in 4 steps

1

Create bucket & API key

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).

2

Write backend.tf

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).

3

terraform init -migrate-state

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.

4

terraform plan with the lockfile

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.

State can contain secrets — keep it clean

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.

As a Terraform backend: AWS S3 vs. Wasabi vs. intercolo i3

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

Migrating from AWS S3: just swap endpoints and region

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.

Migrating from AWS S3: just swap endpoints and region

Fits your existing workflow

Terraform & OpenTofu

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.

CI/CD pipelines

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.

Workspaces & environments

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.

Strong read-after-write

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.

Nous migrons vos données pour vous

Notre expérience parle d'elle-même : nous avons réalisé avec succès des migrations impliquant plusieurs pétaoctets de données et plus de 500 millions de fichiers.

500M+
files migrated
Petabytes
of data migrated
since 2006
in business

Encore des questions ?

No. With use_lockfile = true the S3 backend handles locking itself via a .tflock object and conditional S3 writes. The old dynamodb_table line goes away entirely — no second service, no extra bill. intercolo i3 supports the conditional writes this requires.
Native use_lockfile locking is available from HashiCorp Terraform 1.10 and from OpenTofu 1.8. Older versions still need the DynamoDB-based lock. The endpoints = { s3 = ... } block is the modern syntax and replaces the earlier, deprecated endpoint field.
Because de-fra.i3storage.com is a non-AWS endpoint. skip_credentials_validation skips the AWS STS check, skip_requesting_account_id the IAM account lookup, and skip_region_validation the check against the AWS region list (de-fra is not an AWS region). Without these flags the init run would fail against AWS-specific validations.
There is no storage-side at-rest encryption; transport runs over TLS. Since a state can contain plaintext secrets, keep secrets out of the state via a secret manager/Vault where you can. To encrypt the state itself, use OpenTofu 1.7+ client-side state encryption or a SOPS/age workflow in your pipeline.
In your existing backend "s3" block set region to de-fra, add endpoints = { s3 = "https://de-fra.i3storage.com" }, the skip_ flags and use_lockfile = true, remove dynamodb_table, and point the keys at your SigV4 pair. Then terraform init -migrate-state copies the state to Frankfurt. Resources and modules stay unchanged.
Both work: path-style https://de-fra.i3storage.com/<bucket> and virtual-hosted https://<bucket>.de-fra.i3storage.com. For the terraform s3 backend, endpoints = { s3 = "https://de-fra.i3storage.com" } with region = de-fra is enough; if needed you can steer the addressing style via use_path_style.
Via separate key prefixes or Terraform workspaces plus a dedicated SigV4 key pair and ideally a dedicated bucket per environment (dev/stage/prod). That keeps each state isolated, the native lock prevents concurrent runs per state, and the blast radius of a compromised key stays small.
1 mois d'essai gratuit

Essayez notre stockage compatible S3 pendant 1 mois sans frais.