Skill v1.0.1
currentAutomated scan100/100+4 new
name: opentofu-migration description: Migrate from Terraform to OpenTofu with state compatibility, provider registry setup, and CI/CD pipeline updates. Use when adopting the open-source Terraform fork or evaluating license-free IaC. category: devops risk: critical source: https://github.com/BagelHole/DevOps-Security-Agent-Skills source_repo: BagelHole/DevOps-Security-Agent-Skills source_type: community date_added: '2026-09-20' license: MIT license_source: https://github.com/BagelHole/DevOps-Security-Agent-Skills/blob/main/LICENSE compatibility: Requires the relevant OS/platform tooling and privileged access where noted. Docs-only; helper scripts and templates not bundled. metadata: author: devops-skills version: '1.0'
OpenTofu Migration
Migrate infrastructure-as-code from HashiCorp Terraform to the open-source OpenTofu fork.
When to Use This Skill
Use this skill when:
- Migrating from Terraform to OpenTofu for licensing reasons
- Setting up a new IaC project and evaluating OpenTofu vs Terraform
- Updating CI/CD pipelines to use OpenTofu
- Configuring the OpenTofu provider registry
Prerequisites
- Existing Terraform codebase (0.13+)
- OpenTofu CLI installed
- State backend access (S3, GCS, Azure Blob, etc.)
Install OpenTofu
# macOSbrew install opentofu# Linux (Debian/Ubuntu)curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh \-o install-opentofu.shchmod +x install-opentofu.sh./install-opentofu.sh --install-method debrm install-opentofu.sh# Linux (RPM)./install-opentofu.sh --install-method rpm# Dockerdocker run --rm -v $(pwd):/workspace -w /workspace \ghcr.io/opentofu/opentofu:latest init# Verify installationtofu --version
Migration Checklist
1. Verify Compatibility
# OpenTofu reads Terraform state files directly — no migration needed# Check exact Terraform/OpenTofu versions and their current migration compatibilityterraform version# Run plan with OpenTofu against existing statetofu inittofu plan
2. Replace CLI Commands
| Terraform | OpenTofu | |
|---|---|---|
terraform init | tofu init | |
terraform plan | tofu plan | |
terraform apply | tofu apply | |
terraform destroy | tofu destroy | |
terraform fmt | tofu fmt | |
terraform validate | tofu validate | |
terraform state | tofu state | |
terraform import | tofu import |
3. Update Provider Lock File
# Preserve and review the provider lock file; avoid changing providers during migration.tofu init# Verify providers resolve correctlytofu providers
4. Update State Backend
State files are compatible — no migration needed. Just verify:
# backend.tf — works identically with OpenTofuterraform {backend "s3" {bucket = "mycompany-tfstate"key = "prod/infrastructure.tfstate"region = "us-east-1"dynamodb_table = "terraform-locks"encrypt = true}}
# Verify state accesstofu inittofu state list
5. Provider Registry
OpenTofu uses its own registry but mirrors most Terraform providers:
# versions.tfterraform {required_version = ">= 1.6.0"required_providers {aws = {source = "hashicorp/aws"version = "~> 5.0"}kubernetes = {source = "hashicorp/kubernetes"version = "~> 2.25"}# OpenTofu-specific providersrandom = {source = "hashicorp/random"version = "~> 3.6"}}}
OpenTofu-Specific Features
State Encryption (Not in Terraform)
# OpenTofu supports native state encryptionterraform {encryption {key_provider "pbkdf2" "my_key" {passphrase = var.state_passphrase}method "aes_gcm" "encrypt" {keys = key_provider.pbkdf2.my_key}state {method = method.aes_gcm.encryptenforced = true}plan {method = method.aes_gcm.encryptenforced = true}}}
Early Variable/Local Evaluation
# OpenTofu allows variables in backend config and module sourcesterraform {backend "s3" {bucket = var.state_bucket # Works in OpenTofu, not Terraformkey = "${var.project}/terraform.tfstate"region = var.aws_region}}
CI/CD Pipeline Updates
GitHub Actions
# .github/workflows/tofu.ymlname: OpenTofuon:pull_request:paths: ["infra/**"]push:branches: [main]paths: ["infra/**"]permissions:id-token: writecontents: readpull-requests: writejobs:plan:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- name: Setup OpenTofuuses: opentofu/setup-opentofu@v1with:tofu_version: "1.8.0"- name: Configure AWS credentialsuses: aws-actions/configure-aws-credentials@v4with:role-to-assume: arn:aws:iam::123456789:role/tofu-deployaws-region: us-east-1- name: Initrun: tofu initworking-directory: infra/- name: Planid: planrun: tofu plan -no-color -out=tfplanworking-directory: infra/- name: Comment PR with planif: github.event_name == 'pull_request'uses: actions/github-script@v7with:script: |const output = `#### OpenTofu Plan\`\`\`See the private revision-bound plan artifact; do not interpolate plan text into JavaScript.\`\`\``;github.rest.issues.createComment({issue_number: context.issue.number,owner: context.repo.owner,repo: context.repo.repo,body: output.substring(0, 65536)});apply:needs: planif: github.ref == 'refs/heads/main'runs-on: ubuntu-latestenvironment: productionsteps:- uses: actions/checkout@v4- uses: opentofu/setup-opentofu@v1- uses: aws-actions/configure-aws-credentials@v4with:role-to-assume: arn:aws:iam::123456789:role/tofu-deployaws-region: us-east-1# Prerequisite: retrieve the protected, reviewed plan artifact for this exact# revision/workspace/backend using the same pinned OpenTofu/provider versions.- run: tofu init && tofu apply tfplanworking-directory: infra/
GitLab CI
# .gitlab-ci.ymlstages: [validate, plan, apply]variables:TOFU_VERSION: "1.8.0".tofu-base:image: ghcr.io/opentofu/opentofu:${TOFU_VERSION}before_script:- tofu initvalidate:extends: .tofu-basestage: validatescript:- tofu fmt -check- tofu validateplan:extends: .tofu-basestage: planscript:- tofu plan -out=tfplanartifacts:paths: [tfplan]apply:extends: .tofu-basestage: applyscript:- tofu apply tfplanwhen: manualonly: [main]dependencies: [plan]
Coexistence Strategy
If you need both tools during migration:
# Use aliases to avoid conflictsalias tf="terraform"alias tofu="tofu"# Or use direnv per-project# .envrcexport PATH="/opt/opentofu/bin:$PATH"# Wrapper script for gradual migration#!/bin/bashif [ -f ".use-opentofu" ]; thenexec tofu "$@"elseexec terraform "$@"fi
Troubleshooting
| Issue | Solution | |
|---|---|---|
| Provider not found | Inspect the existing lock/source and registry availability before any separately reviewed upgrade | |
| State lock conflict | Same as Terraform — check DynamoDB/blob lease | |
| Version constraint error | Choose a tested version constraint compatible with the actual configuration | |
| Backend migration | State is compatible — just run tofu init | |
| Missing provider credentials | Same env vars work (AWS_*, GOOGLE_*, ARM_*) |
Related Skills
- terraform-aws (
terraform-aws) — AWS IaC patterns (works with both) - terraform-azure (
terraform-azure) — Azure IaC patterns - terraform-gcp (
terraform-gcp) — GCP IaC patterns - policy-as-code (
policy-as-code) — OPA policy checks for IaC
Limitations
- Infrastructure commands can disrupt services: confirm target host/scope and have backups/snapshots before mutating state.
- Docs-only import: upstream scripts and templates not bundled.
Before migration, back up configuration and state using the backend’s protected procedures; prohibit concurrent Terraform/OpenTofu writers. First compare no-change plans with unchanged provider versions. CI plan/apply fragments are incomplete without secure artifact upload/download, revision/hash binding, protected approval and an apply role unavailable to untrusted PR code; do not silently re-plan during apply. Plan/state files can contain secrets and must not be posted publicly. See OpenTofu migration guide.