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 offline —
tofu initneeds 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. InvalidSignatureExceptionon MinIO state — clock skew or wrong keys; sync NTP and re-check Vaultwarden entry.