CI/CD Integration, GitOps, and Automation Workflows

Authors

K19G

grok-bot

Updated

October 31, 2026

CI/CD Integration, GitOps, and Automation Workflows

The production habit is: a commit produces a plan, a human approves the plan, and tofu apply runs that plan file. This chapter is that pipeline on GitHub Actions, the same shape on other CI systems, and what GitOps does and does not mean for OpenTofu.

Learning goals

  • Split plan and apply so the applied plan is the reviewed plan
  • Wire a GitHub Actions workflow that uses OpenTofu 1.13.2 and a role, not a key
  • Keep approval in front of apply
  • Say where Argo CD and Flux fit, and where they do not apply a .tf file

The Infrastructure Pipeline: Commit, Plan, Review, Apply

One root module, one pipeline:

  1. On a pull request: tofu fmt -check, tofu init -backend=false or init with read-only state credentials, tofu validate, scanner, tofu plan -out=tfplan. Comment the plan summary on the request
  2. A human reads the plan. Destroy lines and IAM changes are the lines to read first
  3. On merge to the branch that is allowed to apply: init with the real backend, plan again, apply the new plan, or apply a plan that was stored and hashed if your change-control rules require the exact bytes from the review
  4. The job fails if the workspace, the backend key, or the OpenTofu version is not the one the repository pinned

Re-planning on merge is the common, honest default. The configuration that merges is the configuration you plan. A plan file saved from the pull request goes stale if main moved. If you must apply the exact reviewed bytes, merge by fast-forward only and apply the stored plan before anything else lands. Record the plan digest in the approval.

Never tofu apply -auto-approve on a pull request from a fork. Credentials available to untrusted diffs are credentials you have given away. The plan job on a fork can run without credentials when the module’s tests allow it, and the apply job runs only on the protected branch.

GitHub Actions Integration: Complete Workflow Examples

Pin the tool. mise or the GitHub release action both work. This sketch uses the official setup style and OIDC to AWS. The role trust policy allows this repository’s protected environment, not every branch.

name: tofu
on:
  pull_request:
  push:
    branches: [main]

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

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: opentofu/setup-opentofu@v1
        with:
          tofu_version: 1.13.2
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/tofu-plan-pets
          aws-region: eu-west-1
      - run: tofu init
      - run: tofu plan -out=tfplan -no-color
        working-directory: roots/pets

Check the current major of opentofu/setup-opentofu and aws-actions/configure-aws-credentials when you copy this. Actions move. The pins that matter for the tool itself are tofu_version: 1.13.2 and the commit or tag of the action, which you set to a full SHA in a real repository so a tag cannot move under you.

Apply is a second job, needs: plan, if: github.ref == 'refs/heads/main', with the environment production requiring reviewers. It runs tofu apply -auto-approve tfplan only when tfplan was produced in that same workflow by the merge commit. A pull request job stops at plan.

The pet root can use the plan job with no AWS step at all. Keep the cloud role off the toy.

GitLab CI, Jenkins, and Azure DevOps: Multi-Platform Pipeline Patterns

The platform changes. The stages do not.

Stage GitLab CI Jenkins Azure DevOps
Pin Image ghcr.io/opentofu/opentofu:1.13.2 The same image, or a mise install in the agent The same image
Identity id_tokens and the cloud’s OIDC Workload identity on the agent, not a stored access key Service connection with federation
Plan Merge request pipeline PR build PR validation
Approve Protected environment, required approvals Input step before the apply job Environment check on the production stage
Apply Main-branch job Job after the input, on main Stage with a condition on the main branch

Write the plan to the job log in a redacted form. tofu show tfplan respects sensitive values better than printing state. Store the plan file as an artifact only for the minutes between plan and apply, encrypted at rest by the CI system, and delete it.

A Jenkins agent that holds a static cloud key for every job is the pattern these pipelines exist to leave. One role per root, assumed for that job, shows up in CloudTrail as that role.

GitOps With OpenTofu: Declarative Deployment Using ArgoCD and Flux

GitOps means the git repository is the desired state, and a running process converges the world to it. For Kubernetes, Argo CD and Flux are that process. They apply manifests. They do not natively apply OpenTofu.

Three layouts are honest:

  • Pipeline GitOps. Git is the source. The CI system in the previous sections is the reconciler. This is the default for cloud accounts and for on-premises appliances. You do not need Argo CD to say you deploy from git
  • OpenTofu provisions the cluster, Flux or Argo CD provisions the workloads. The cluster’s state lives in an OpenTofu root. The workloads live in a git repository those controllers watch. The handoff is an output: a cluster endpoint and a place the controller is installed. Do not also manage the same Deployment with OpenTofu
  • A controller runs OpenTofu. Projects such as the tofu-controller (the OpenTofu-oriented descendant of Weave’s tf-controller) watch a git repository and run plan and apply from inside the cluster. That is GitOps in the strict sense. It belongs when the cluster is already the control plane you operate. It is an extra system to secure: the controller’s cloud role, the source repository it can read, and what it does without a human. Put the approval gate in front of it the same way you would in CI, or accept that merge equals apply and say so

Do not point Argo CD at a directory of .tf files and expect a plan. Do not run a second reconciler against a state the pipeline also applies.

Approval Gates and Change Management: Human Review in Automated Workflows

The gate is a protected environment or an explicit approval job between plan and apply. The approver sees the plan summary, the commit, and the OpenTofu version. The approver is not the only author of the change when the repository’s rules require two people. For a one-person lab, the gate can be you, and the pipeline still runs the apply so the laptop is not the only place with credentials.

Exceptions during an incident are a break-glass role that is logged and time-limited. The commit still lands. Applying from a laptop with the break-glass role and never pushing the configuration is how state and git diverge. Chapter 10’s import and chapter 7’s drift are the cleanup if that already happened.

Destructive plans need a louder gate: a second reviewer, or a rule that fails the job unless a label such as allow-destroy is present. Encode “this plan destroys” as a check on the plan JSON so it does not depend on someone scrolling.

Parallel Execution and Performance Optimization: Scaling Plans and Applies

OpenTofu walks the graph and applies independent resources in parallel. -parallelism=n raises or lowers that fan-out. The default is enough until an API rate-limits you or a provider starts failing under load. Lower it before you raise it. A higher number does not fix a slow data source.

What actually speeds a large root:

  • Split roots along the seams in chapter 14 so one plan is not the entire company
  • Stop using depends_on on whole modules when an output reference is enough
  • Keep data sources off moving targets so plans are stable and caches are meaningful
  • Run tofu plan only in the roots the change touches. A monorepo of many roots does not plan all of them on every commit. Path filters do that
  • Init once per job and reuse the provider cache directory when the runner allows it. A cold init of every provider on every plan dominates small configurations

-refresh=false skips the refresh and plans against state only. It is faster and it is wrong when the world has drifted. Use it for a local syntax experiment, not for the production apply. There is a narrower “minimal refresh” idea on some Terraform lines. Do not assume the OpenTofu flag set matches Terraform 1.17. Check tofu plan -help on 1.13.2.

State locking already serialises applies of one root. Do not also serialise unrelated roots. They have different locks. Parallel pipelines across roots are safe. Parallel pipelines against one state are the lock doing its job: the second waits or fails.

Summary

  • Pull requests plan. The protected branch applies the plan it just created
  • Pin OpenTofu to 1.13.2 in the workflow. Assume a role. Do not store a cloud key
  • GitLab, Jenkins, and Azure DevOps use the same stages
  • Argo CD and Flux reconcile Kubernetes. A pipeline or a tofu controller reconciles OpenTofu
  • Approval sits between plan and apply. A destroy is a louder approval
  • Speed comes from smaller roots and fewer false edges, not from a larger parallelism number