Remote State Configuration with TrueNAS MinIO
This guide explains how to configure and use TrueNAS MinIO for remote state storage with OpenTofu.
Table of Contents
- Overview
- Benefits
- Prerequisites
- Setup Process
- Migration
- Switching Between Local and Remote State
- Security Best Practices
- Troubleshooting
Overview
Remote state storage provides disaster recovery and collaboration capabilities by storing your OpenTofu state file in TrueNAS MinIO (S3-compatible object storage) instead of locally.
This implementation uses partial backend configuration:
- Static backend settings are defined in
backend.tf - Sensitive credentials are stored in
backend-config.tfvars(gitignored) - Backend is initialized using:
tofu init -backend-config=backend-config.tfvars
Benefits
- Disaster Recovery: State survives local infrastructure loss
- State History: MinIO versioning enabled (can rollback to previous states)
- Encryption:
- In transit: HTTPS/TLS
- Self-Hosted: TrueNAS MinIO (no external S3 dependency)
- State Locking: S3 native locking prevents concurrent modifications
- Cost-Effective: No external cloud storage costs
- S3 Compatibility: Works with standard S3 tools and workflows
- Security: Credentials stored in gitignored
backend-config.tfvars
Prerequisites
1. TrueNAS MinIO Deployment
MinIO must be deployed and accessible.
Get MinIO credentials from Kubernetes secret:
# Access Key
kubectl get secret longhorn-minio-credentials -n litellm \
-o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 -d
# Secret Key
kubectl get secret longhorn-minio-credentials -n litellm \
-o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d
# Endpoint
kubectl get secret longhorn-minio-credentials -n litellm \
-o jsonpath='{.data.AWS_ENDPOINTS}' | base64 -d
Use these credentials in your backend-config.tfvars file.
2. Generate Encryption Passphrase
Generate a secure passphrase for state encryption (minimum 16 characters, recommended 32+):
openssl rand -base64 32
Store this passphrase securely in your password manager. This will go in terraform.tfvars.
3. Choose Bucket Name
Bucket names must be unique within your MinIO instance. Example: homelab-terraform-state
Setup Process
Configure Main Tofu for MinIO Remote State
-
Navigate to main tofu directory:
cd /path/to/homelab/tofu/ -
Create
backend-config.tfvarsfrom the example:cp backend-config.tfvars.example backend-config.tfvars -
Edit
backend-config.tfvarswith your MinIO credentials:# S3-compatible bucket configuration
bucket = "homelab-terraform-state"
key = "proxmox/terraform.tfstate"
region = "us-east-1" # Arbitrary for MinIO, validation is skipped
# MinIO endpoint (your TrueNAS MinIO URL)
endpoint = "https://truenas.yourdomain.com:9000"
# MinIO credentials (obtain from TrueNAS MinIO console)
access_key = "YOUR_MINIO_ACCESS_KEY"
secret_key = "YOUR_MINIO_SECRET_KEY"Note:
backend-config.tfvarsis gitignored to protect your credentials. -
Create the MinIO bucket (if it doesn't exist):
# Using AWS CLI
AWS_ACCESS_KEY_ID="your-access-key" \
AWS_SECRET_ACCESS_KEY="your-secret-key" \
aws s3 mb s3://homelab-terraform-state \
--endpoint-url https://truenas.yourdomain.com:9000 \
--no-verify-ssl
# Or use MinIO Client (mc)
mc mb minio/homelab-terraform-stateNote: The bucket must exist before running
tofu init -migrate-state. -
Backup local state (if exists):
cp terraform.tfstate terraform.tfstate.backup.before-minio-migration -
Migrate to MinIO remote state:
tofu init -backend-config=backend-config.tfvars -migrate-stateWhen prompted, confirm migration by typing:
yes -
Verify migration:
tofu state list
tofu show
# Verify state exists in MinIO
AWS_ACCESS_KEY_ID="your-access-key" \
AWS_SECRET_ACCESS_KEY="your-secret-key" \
aws s3 ls s3://homelab-terraform-state/proxmox/ \
--endpoint-url https://truenas.yourdomain.com:9000 \
--no-verify-ssl -
Optional cleanup (after successful verification):
# Keep backups, remove active local state
rm -f terraform.tfstate
Migration
From Local to MinIO Remote State
See Configure Main Tofu for MinIO Remote State above.
Special Case: Migrating Unencrypted Local State
If your local state was created without encryption, you'll need to temporarily configure an unencrypted fallback method during migration:
-
Add unencrypted fallback to
providers.tf(temporarily):encryption {
key_provider "pbkdf2" "encryption_passphrase" {
passphrase = var.encryption_passphrase
}
method "aes_gcm" "encryption_method" {
keys = key_provider.pbkdf2.encryption_passphrase
}
method "unencrypted" "migration" {}
state {
method = method.aes_gcm.encryption_method
fallback {
method = method.unencrypted.migration
}
enforced = false
}
plan {
method = method.aes_gcm.encryption_method
fallback {
method = method.unencrypted.migration
}
enforced = false
}
} -
Run migration:
echo "yes" | tofu init -backend-config=backend-config.tfvars -migrate-state -
Remove fallback and enforce encryption in
providers.tf:encryption {
key_provider "pbkdf2" "encryption_passphrase" {
passphrase = var.encryption_passphrase
}
method "aes_gcm" "encryption_method" {
keys = key_provider.pbkdf2.encryption_passphrase
}
state {
method = method.aes_gcm.encryption_method
enforced = true
}
plan {
method = method.aes_gcm.encryption_method
enforced = true
}
} -
Verify encryption is working:
tofu apply -refresh-only
After this process, your remote state will be encrypted even though the source local state was not.
From MinIO Remote to Local State
-
Backup current state:
tofu state pull > terraform.tfstate.backup-$(date +%Y%m%d-%H%M%S) -
Comment out backend block in
backend.tf:# terraform {
# backend "s3" {
# ...
# }
# } -
Migrate to local:
tofu init -migrate-state -
Verify:
ls -la terraform.tfstate
tofu state list
Switching Between Local and Remote State
Enable MinIO Remote State
- Ensure
backend-config.tfvarsis configured with your credentials - Uncomment backend block in
backend.tf(if commented) - Run:
tofu init -backend-config=backend-config.tfvars -migrate-state
Disable MinIO Remote State
- Comment out backend block in
backend.tf - Run:
tofu init -migrate-state
Note: Always backup state before switching!
Security Best Practices
1. Secure Credentials
- Never commit
terraform.tfvarsorbackend-config.tfvarsto git (they're in.gitignore) - Use strong passphrases for
encryption_passphraseinterraform.tfvars(minimum 16 chars) - Rotate MinIO keys periodically via TrueNAS console and update
backend-config.tfvars - Limit MinIO key permissions to specific bucket only
2. Encryption
OpenTofu encrypts state using:
- Algorithm: pbkdf2 (key derivation, 600k iterations) + AES-GCM (encryption)
- Configuration: Set
encryption_passphraseinterraform.tfvars - Key requirement: Same passphrase must be used for all future operations
3. State File Security
- State files contain sensitive information (passwords, keys, IPs)
- Encryption passphrase (in
terraform.tfvars) is required to decrypt state - Store passphrase securely (password manager, vault)
- Different passphrase for prod vs dev environments
4. Access Control
- Limit MinIO key access to state bucket only
- Use separate keys for different environments
- Audit MinIO access logs via TrueNAS console
5. Backup Strategy
- MinIO versioning: Configure bucket versioning for state rollback
- Local backups: Before major changes
- Test restores: Verify backups work
Troubleshooting
Error: "bucket already exists"
Cause: Bucket name conflict in MinIO.
Solution: MinIO will use existing bucket automatically. No action required.
Error: "Access Denied"
Cause: Invalid or insufficient MinIO credentials.
Solutions:
- Verify
access_keyandsecret_keyinbackend-config.tfvarsare correct - Ensure key has read/write permissions to buckets
- Check key hasn't been deleted or revoked in MinIO console
- Re-run
tofu init -backend-config=backend-config.tfvarsafter fixing credentials
Error: "Invalid encryption passphrase"
Cause: Incorrect or missing encryption passphrase.
Solutions:
- Verify
encryption_passphraseinterraform.tfvarsmatches one used to create/encrypt state - Check for typos in passphrase
- Ensure passphrase is set in
terraform.tfvars
Error: "Backend config contains sensitive values"
Cause: Attempting to use variables in backend block or sensitive values in backend.tf.
Solutions:
- Ensure you're using
backend-config.tfvarsfor credentials, notbackend.tf - Run
tofu init -backend-config=backend-config.tfvarsto properly configure backend - Verify
backend.tfdoesn't contain hardcoded credentials
Error: "S3 bucket does not exist"
Cause: The MinIO bucket hasn't been created yet.
Solutions:
- Create the bucket using AWS CLI:
AWS_ACCESS_KEY_ID="your-access-key" \
AWS_SECRET_ACCESS_KEY="your-secret-key" \
aws s3 mb s3://homelab-terraform-state \
--endpoint-url https://truenas.yourdomain.com:9000 \
--no-verify-ssl - Or create via MinIO Console at
https://truenas.yourdomain.com:9001 - Verify bucket exists:
AWS_ACCESS_KEY_ID="your-access-key" \
AWS_SECRET_ACCESS_KEY="your-secret-key" \
aws s3 ls --endpoint-url https://truenas.yourdomain.com:9000 --no-verify-ssl - Retry migration:
tofu init -backend-config=backend-config.tfvars -migrate-state
Error: "encountered unencrypted payload without unencrypted method configured"
Cause: Trying to migrate unencrypted local state with encryption enforced.
Solutions:
- See the Special Case: Migrating Unencrypted Local State section above
- Temporarily add an unencrypted fallback method to
providers.tf - After migration, remove the fallback and enforce encryption
State migration fails
Symptoms: tofu init -migrate-state errors
Solutions:
- Verify backend configuration in
backend-config.tfvars:bucketmatches desired bucket nameendpointURL is correct and accessibleaccess_keyandsecret_keyare valid
- Check MinIO connectivity:
curl -k https://truenas.yourdomain.com:9000/minio/health/live - Verify local state exists before migration
- Ensure you're using:
tofu init -backend-config=backend-config.tfvars -migrate-state
Cannot access remote state
Symptoms: tofu plan fails to read state
Solutions:
- Verify MinIO credentials in
backend-config.tfvarsare correct - Check
encryption_passphraseinterraform.tfvarsmatches - Ensure MinIO bucket exists and is accessible
- Verify network connectivity to MinIO
- Re-initialize if needed:
tofu init -backend-config=backend-config.tfvars -reconfigure
State lock errors
Note: MinIO S3-compatible API supports state locking via native S3 locking.
If lock errors occur:
- Check for other
tofu applyprocesses running - Verify no concurrent operators
- Force unlock (rare):
tofu force-unlock <LOCK_ID>(use with caution)
State Versioning
MinIO bucket versioning enables state rollback:
Enable Bucket Versioning
- Access MinIO console:
https://truenas.peekoff.com:9001 - Navigate to: Buckets → Select bucket → Versioning
- Enable versioning
Rollback to Previous State Version
- List versions in MinIO console
- Download desired version
- Restore locally:
cp terraform.tfstate.restored terraform.tfstate - Verify state:
tofu state list
Cost Considerations
TrueNAS MinIO (self-hosted):
- Storage: No direct cost (uses TrueNAS storage)
- Network: Local network traffic only
- Maintenance: TrueNAS maintenance overhead
- Backup: External backup strategy required for MinIO bucket
References
Support
For issues or questions:
- Review OpenTofu logs:
TF_LOG=DEBUG tofu plan - Check MinIO console for bucket status and access logs
- Verify credentials and configuration
- Test MinIO connectivity:
curl https://truenas.peekoff.com:9000/minio/health/live