Deploying OpenTofu on CI/CD Pipelines: A Practical Migration Guide from Terraform

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Approach Comparison: Terraform vs. OpenTofu in Automated Pipelines

On August 10, 2023, HashiCorp shifted Terraform from the open-source MPL 2.0 license to the Business Source License (BSL 1.1). That single licensing shift triggered immediate questions for platform teams. Did building internal developer platforms violate BSL terms?

Could managed service providers continue offering Terraform-based automation to clients? In response, companies including Gruntwork, Spacelift, and Env0 launched OpenTofu. Now hosted by the Linux Foundation, OpenTofu provides an independently governed fork with permanent open-source licensing.

DevOps teams evaluating continuous delivery today face a clear choice:

  • Staying with Terraform: You preserve your current tooling and HashiCorp Cloud integration. However, your legal and platform teams must audit all commercial automation, competitive offerings, and internal tooling for BSL compliance.
  • Switching to OpenTofu: OpenTofu forked from Terraform 1.5.6 and remains fully compatible with Terraform 1.5.7 configurations. Existing HCL files, remote state backends, and community providers run without modification under the MPL 2.0 license.

Both binaries share identical execution loops. They pull provider plugins, compute resource diffs against state, and apply approved changes. Migrating a CI/CD pipeline requires three steps: swap the CLI binary, point to the OpenTofu registry, and verify state backend locking.

Pros & Cons of Migrating to OpenTofu

Before updating runners across 50 repositories, weigh the operational payoffs against the friction of switching.

The Advantages

  • Independent Foundation Governance: OpenTofu operates under the Linux Foundation. Roadmaps, RFCs, and release cadences are vendor-neutral. No single company can alter the license on a future release.
  • Drop-in CLI Parity: Commands map 1:1. Run tofu init, tofu plan, and tofu apply using the exact arguments and flags your team already runs with Terraform.
  • Seamless State Compatibility: If your infrastructure runs on Terraform 1.5.7 or earlier, OpenTofu reads your existing State Format v4 directly. You avoid complex JSON schema conversions or state resets.
  • Client-Side State Encryption: Starting in version 1.7.0, OpenTofu introduced native state encryption. You can encrypt sensitive variables and secrets directly on your runner using AWS KMS, GCP KMS, or AES-GCM before writing to remote object storage.

The Potential Drawbacks

  • Feature Divergence: Compatibility remains strong for core HCL. However, proprietary features introduced in Terraform 1.7 and later (such as test mocks and specific provider functions) do not carry over automatically.
  • CI Ecosystem Packaging: HashiCorp maintains pre-baked setup steps in some cloud marketplaces. Switching to OpenTofu requires updating your pipeline YAML to use the official opentofu/setup-opentofu action or container image.

For most platform teams, the migration takes less than two hours per repository. The payoff is immediate: total independence from upstream commercial licensing restrictions.

Recommended Setup for an OpenTofu CI/CD Pipeline

Running IaC in CI/CD demands strict isolation. A secure architecture relies on four baseline guardrails:

  1. Keyless OIDC Authentication: Eliminate static cloud credentials from repository secrets. Use OpenID Connect (OIDC) to federate authentication between your CI runner and your cloud provider. AWS IAM, Google Cloud Workload Identity, and Azure Entra ID all issue short-lived (typically 1-hour) tokens directly to verified pipeline jobs.
  2. State Storage with Distributed Locking: Store your state in dedicated remote storage, such as Amazon S3 backed by DynamoDB or Google Cloud Storage with object locking enabled. Fast locking prevents race conditions and corrupt states when parallel pull requests trigger overlapping plans.
  3. Separated Plan and Apply Stages: Run tofu plan automatically on pull requests. Export the plan summary directly into PR comments so reviewers can inspect diffs before merging. Restrict tofu apply strictly to merged commits on the main branch.
  4. Strict Version Pinning: Explicitly pin both the OpenTofu binary (e.g., 1.8.2) and provider versions (e.g., AWS provider ~> 5.40). Pinning prevents automated builds from breaking when upstream registries publish major version updates.

Implementation Guide: Migrating from Terraform to OpenTofu in GitHub Actions

Follow these steps to convert an existing Terraform repository to a hardened OpenTofu GitHub Actions workflow.

Step 1: Test Your Local Project with the OpenTofu CLI

Verify that your code compiles locally before committing pipeline changes. Install OpenTofu and test initialization:

# Install OpenTofu on Ubuntu/Debian using the official installer script
curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh
chmod +x install-opentofu.sh
./install-opentofu.sh --install-method deb
rm -f install-opentofu.sh

# Validate your project directory
cd my-infrastructure-repo/
tofu init
tofu plan

If the plan completes without schema warnings or provider failures, your codebase is ready for automated execution.

Step 2: Update Your Versions Configuration

Open versions.tf. Set the required version constraint to support OpenTofu releases and maintain an encrypted S3 backend:

terraform {
  # Accepts OpenTofu 1.6+ or Terraform 1.5.7
  required_version = ">= 1.5.7"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40"
    }
  }

  backend "s3" {
    bucket         = "corp-infrastructure-tfstate-prod"
    key            = "networking/vpc-production.tfstate"
    region         = "us-east-1"
    dynamodb_table = "corp-tfstate-locks"
    encrypt        = true
  }
}

Step 3: Build the GitHub Actions Pipeline

Create .github/workflows/opentofu.yml. This workflow authenticates via AWS OIDC, checks formatting, posts plan output to pull requests, and applies changes on merge.

name: "OpenTofu Infrastructure Pipeline"

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  tofu:
    name: "OpenTofu Plan & Apply"
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Configure AWS Credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-opentofu
          aws-region: us-east-1

      - name: Setup OpenTofu
        uses: opentofu/setup-opentofu@v1
        with:
          tofu_version: 1.8.2

      - name: Initialize OpenTofu
        run: tofu init

      - name: Validate Formatting
        run: tofu fmt -check

      - name: Run Speculative Plan
        id: plan
        if: github.event_name == 'pull_request'
        run: |
          tofu plan -no-color -out=plan.tfplan
        continue-on-error: false

      - name: Publish Plan Summary to Pull Request
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const body = `#### OpenTofu Plan Result: \`Success\`
            * **Pushed by:** @${context.actor}
            * **Action:** \`${context.eventName}\`
            * **Target Branch:** \`${context.payload.pull_request.base.ref}\``;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: body
            });

      - name: Apply Approved State
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: tofu apply -auto-approve

Step 4: Execute Cutover and Validate State Integrity

Deploy the updated workflow to a staging branch first. Inspect the GitHub Actions runner log to confirm that tofu init acquired the DynamoDB lock and read your S3 state bucket without errors.

Before rolling this out across production, complete two essential safeguards:

  • Back up remote state: Run aws s3 cp s3://corp-infrastructure-tfstate-prod/ ./backup-state/ --recursive to capture an exact snapshot before the first run.
  • Standardize developer workstations: Have all team members install tofu locally. If a developer runs a newer proprietary Terraform binary (like Terraform 1.9+) locally, it could advance the state schema version and lock out your OpenTofu pipeline.
Share: