replica-omnisciente/docs/guides/cli/terraform-opentofu.md

3.7 KiB

terraform / tofu (Terraform + OpenTofu)

What it is

Infrastructure-as-code CLIs. OpenTofu (tofu) is the open-source (MPL-2.0) fork of Terraform and is our default; terraform is kept for modules or providers that lag Tofu compatibility. CLIs are drop-in compatible for current Terraform 1.5-era workflows.

Install

OpenTofu (preferred — official apt repo):

curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh
chmod +x /tmp/install-opentofu.sh
sudo /tmp/install-opentofu.sh --install-method deb

Terraform (HashiCorp apt repo):

curl -fsSL https://apt.releases.hashicorp.com/gpg \
  | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" \
  | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

Both publish static binaries too — pin versions via required_version in your root module rather than relying on whatever is installed.

Authenticate

Per provider, via environment variables — keep the real secrets in Vaultwarden (https://vault.portugalfuturista.org) and load them into the shell:

export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=...     # aws provider
export ARM_CLIENT_ID=... ARM_CLIENT_SECRET=...             # azurerm

Configure for this environment

Dev targets are the self-hosted emulators on lattepanda (192.168.0.40), so provider blocks point at them:

provider "aws" {
  region                      = "us-east-1"
  access_key                  = "test"
  secret_key                  = "test"
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true
  endpoints {
    s3     = "http://192.168.0.40:4566"   # LocalStack
    lambda = "http://192.168.0.40:4566"
  }
}

Self-hosted equivalent

State on MinIO via the S3 backend (backend.tf):

terraform {
  backend "s3" {
    bucket                      = "tf-state"
    key                         = "lab/terraform.tfstate"
    region                      = "us-east-1"
    endpoint                    = "http://192.168.0.40:9000"
    access_key                  = "<minio-access-key>"
    secret_key                  = "<minio-secret-key>"
    skip_credentials_validation = true
    skip_metadata_api_check     = true
    skip_region_validation      = true
    force_path_style            = true
  }
}

Create the bucket first (aws --endpoint-url http://192.168.0.40:9000 s3 mb s3://tf-state), then tofu init. Better still, pass the MinIO keys via AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY at init time so secrets never sit in .tf files.

Aurélio integration

The cli-devops skill runs tofu plan/apply against lab stacks. Connector registry ids: terraform (tool), minio (state backend), aws-localstack (provider target).

Verify

tofu version
# OpenTofu v1.x.y
terraform version
# Terraform v1.x.y
tofu init && tofu validate
# Success! The configuration is valid.

Troubleshooting

  • Error acquiring the state lock — with the MinIO backend there's no DynamoDB lock table; don't run two applies concurrently (MinIO conditional writes give basic protection only).
  • Provider downloads blocked offlinetofu init needs registry access once; use a filesystem mirror or vendor providers into ~/.terraform.d/plugins.
  • LocalStack endpoint ignored — every endpoints {} service you use must be listed; unknown services fall through to real AWS.
  • InvalidSignatureException on MinIO state — clock skew or wrong keys; sync NTP and re-check Vaultwarden entry.