When OpenTofu 1.10 announced OCI support for providers, many thought: "What about state?" Until now, nothing. So I created Ghoten — a focused OpenTofu fork that adds a native ORAS backend and a ready-made GitHub Action.
Name origin: Ghoten blends Git*Hub and OpenTofu, with a nod to Goten from *Dragon Ball Z.
The Problem
153 MB of tfstate in S3 + DynamoDB for locking = overkill for many projects. If your team already has:
- An OCI registry in use (such as GHCR)
- GitHub Actions deploying Terraform/OpenTofu code
Why add another service and its cost?
The Solution: ORAS Backend
Ghoten adds a native oras backend that stores state as OCI artifacts:
terraform {
backend "oras" {
repository = "ghcr.io/acme/infra-state"
}
}
That's the minimal config — one required setting. For production, add sensible defaults:
terraform {
backend "oras" {
repository = "ghcr.io/myorg/infra-state"
compression = "gzip"
lock_ttl = 300
max_versions = 10
}
}
lock_ttl auto-clears stale locks after crashed jobs, and max_versions keeps history without unbounded growth.
Features
- ✅ State, locks, and versions stored as OCI artifacts (content-addressable)
- ✅ Distributed locking with TTL-based stale lock cleanup (no DynamoDB needed)
- ✅ Configurable versioning and retention
- ✅ Retry with exponential backoff for transient errors
- ✅ Rate limiting to respect registry quotas
- ✅ Uses Docker credential helpers and
ghoten login/ Terraform-style host tokens - ✅ Compatible with OpenTofu's native encryption
- ✅ TLS support with custom CA bundles
- ✅ Verified against GHCR and Zot; any OCI-compliant registry expected to work
GitHub Action
The fastest path to OCI-backed state in CI. Install, auth, init, run, PR comments, and job summaries — in one step:
name: Infra Plan
on: pull_request
jobs:
plan:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: vmvarela/ghoten@v1
That's it. Defaults to plan, posts a PR comment, and writes a Job Summary.
For a plan-on-PR / apply-on-merge workflow:
name: Infrastructure
on:
pull_request:
push:
branches: [main]
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions: { contents: read, packages: write, pull-requests: write }
steps:
- uses: actions/checkout@v6
- uses: vmvarela/ghoten@v1
apply:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions: { contents: read, packages: write }
steps:
- uses: actions/checkout@v6
- uses: vmvarela/ghoten@v1
with:
command: apply
Installation
Build from source
git clone https://github.com/vmvarela/ghoten.git
cd ghoten
make build
./ghoten version
Docker image
docker pull ghcr.io/vmvarela/ghoten:<version>
Example with Encryption
terraform {
backend "oras" {
repository = "ghcr.io/myorg/infra-state"
compression = "gzip"
lock_ttl = 300
max_versions = 10
}
encryption {
key_provider "pbkdf2" "main" {
passphrase = var.state_passphrase
}
method "aes_gcm" "main" {
key_provider = key_provider.pbkdf2.main
}
state { method = method.aes_gcm.main }
plan { method = method.aes_gcm.main }
}
}
For production systems, prefer a KMS-backed key provider over PBKDF2 passphrases.
Use Cases
Perfect for:
- Startups with lean infra-as-code
- Personal/side projects with free GHCR
- Air-gapped environments (registry mirror)
- Teams already invested in the OCI ecosystem
- Multi-cloud projects that want one backend
Configuration Reference
| Parameter | Default | Notes |
|---|---|---|
repository |
— |
Required. OCI repo (<registry>/<repo>) |
compression |
none |
none or gzip
|
lock_ttl |
0 |
Seconds; > 0 enables stale-lock cleanup |
max_versions |
0 |
Historical state versions to retain |
retry_max |
2 |
Retry count for transient errors |
retry_wait_min |
1 |
Backoff min (seconds) |
retry_wait_max |
30 |
Backoff max (seconds) |
rate_limit |
0 |
Requests/sec (0 = unlimited) |
insecure |
false |
Skip TLS verification |
ca_file |
— | PEM CA bundle path |
Most settings also accept environment variables (TF_BACKEND_ORAS_*).
Feedback Wanted
What do you think? Would you store state in OCI registries instead of S3? What features would you need to adopt this?
Top comments (0)