diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2b685284e2e5cc7207a33d189e7c9a6690fb763d..7946fbfa11124b987f5fa324e7d17a35e87ccb07 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,22 +6,25 @@ workflow:
     - if: $CI_COMMIT_BRANCH && $CI_COMMIT_REF_PROTECTED == 'true'
 
 include:
+  - local: .gitlab/terraform-test.gitlab-ci.yml
+  - local: .gitlab/tofu-test.gitlab-ci.yml
   - project: "gitlab-org/quality/pipeline-common"
     file:
       - "/ci/danger-review.yml"
 
 variables:
   BASE_IMAGE: "alpine:3.18.4"
-  BUILD_IMAGE_NAME: "$CI_REGISTRY_IMAGE/branches/$CI_COMMIT_REF_SLUG-$TERRAFORM_VERSION:$CI_COMMIT_SHA"
+  TERRAFORM_IMAGE_NAME: "$CI_REGISTRY_IMAGE/branches/$CI_COMMIT_REF_SLUG-$TERRAFORM_VERSION:$CI_COMMIT_SHA"
+  TOFU_IMAGE_NAME: "$CI_REGISTRY_IMAGE/branches/$CI_COMMIT_REF_SLUG-$TOFU_VERSION:$CI_COMMIT_SHA"
   DOCKER_DIND_IMAGE: "docker:24.0.7-dind"
   NODE_IMAGE: "node:lts-slim"
   PLATFORMS: linux/amd64,linux/arm64
-  RELEASE_IMAGE_NAME: "$CI_REGISTRY_IMAGE/releases/$TERRAFORM_VERSION"
   STABLE_IMAGE_NAME: "$CI_REGISTRY_IMAGE/stable:latest"
   STABLE_VERSION: "1.5"
+  STABLE_TOFU_VERSION: "1.6"
   TF_STATE_NAME: ci-$CI_JOB_ID
 
-.versions:
+.terraform-versions:
   parallel:
     matrix:
       - TERRAFORM_BINARY_VERSION: "1.5.7"
@@ -35,6 +38,13 @@ variables:
       - TERRAFORM_BINARY_VERSION: "1.1.9"
         TERRAFORM_VERSION: "1.1"
 
+.tofu-versions:
+  parallel:
+    matrix:
+      # latest version from https://pkgs.alpinelinux.org/packages?name=opentofu
+      - TOFU_BINARY_VERSION: "1.6.0_beta1-r0"
+        TOFU_VERSION: "1.6"
+
 stages:
   - lint
   - build
@@ -82,13 +92,18 @@ commit lint:
 dockerfile check:
   stage: lint
   image: hadolint/hadolint:latest-alpine
+  parallel:
+    matrix:
+      - FILE: Dockerfile.terraform
+      - FILE: Dockerfile.tofu
+
   before_script:
     - hadolint --version
   script:
-    - hadolint Dockerfile
+    - hadolint $FILE
 
-build:
-  extends: .versions
+build terraform:
+  extends: .terraform-versions
   stage: build
   services:
     - "$DOCKER_DIND_IMAGE"
@@ -114,8 +129,36 @@ build:
       --platform "$PLATFORMS"
       --build-arg BASE_IMAGE=$BASE_IMAGE
       --build-arg TERRAFORM_BINARY_VERSION=$TERRAFORM_BINARY_VERSION
-      --file Dockerfile
-      --tag "$BUILD_IMAGE_NAME"
+      --file Dockerfile.terraform
+      --tag "$TERRAFORM_IMAGE_NAME"
+      --provenance=false
+      --push
+      .
+
+build tofu:
+  extends: .tofu-versions
+  stage: build
+  services:
+    - "$DOCKER_DIND_IMAGE"
+  image: "$DOCKER_DIND_IMAGE"
+  before_script:
+    # See note on the `build terraform` job about this image
+    - docker run --rm --privileged tonistiigi/binfmt
+    # Registry auth
+    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
+  script:
+    - docker buildx create --use
+    # NOTE: we disable provenance for now
+    # because it causes troubles with the registry and older clients.
+    # See
+    # - https://gitlab.com/gitlab-org/terraform-images/-/issues/104
+    # - https://gitlab.com/gitlab-org/terraform-images/-/merge_requests/184#note_1328485943
+    - docker buildx build
+      --platform "$PLATFORMS"
+      --build-arg BASE_IMAGE=$BASE_IMAGE
+      --build-arg TOFU_BINARY_VERSION=$TOFU_BINARY_VERSION
+      --file Dockerfile.tofu
+      --tag "$TOFU_IMAGE_NAME"
       --provenance=false
       --push
       .
@@ -132,361 +175,39 @@ upload:
     - if: '$CI_PROJECT_PATH == "gitlab-org/terraform-images" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
       when: manual
 
-.test-base:
-  image: "$BUILD_IMAGE_NAME"
-  before_script:
-    - gitlab-terraform version
-    - jq --version
-  cache:
-    key: "$TERRAFORM_VERSION-$CI_COMMIT_REF_SLUG"
-    paths:
-      - tests/.terraform/
-
-.test:
-  extends:
-    - .test-base
-  before_script:
-    - !reference [.test-base, before_script]
-    - cd tests
-
-.test-tf-root:
-  extends:
-    - .test-base
-  variables:
-    TF_ROOT: tests
-
-test-init:
-  extends:
-    - .test
-    - .versions
-  stage: test-init
-  script:
-    - export DEBUG_OUTPUT=true
-    - gitlab-terraform init
-
-test-init-with-args:
-  extends:
-    - .test
-    - .versions
-  stage: test-init
-  script:
-    - export DEBUG_OUTPUT=true
-    - gitlab-terraform init -get=true -no-color
-
-test-init-with-flags:
-  extends:
-    - .test
-    - .versions
-  stage: test-init
-  script:
-    - export DEBUG_OUTPUT=true
-    - export TF_INIT_FLAGS="-get=true -no-color"
-    - gitlab-terraform init
-
-test-init-with-flags-and-args:
-  extends:
-    - .test
-    - .versions
-  stage: test-init
-  script:
-    - export DEBUG_OUTPUT=true
-    - export TF_INIT_FLAGS="-get=true"
-    - gitlab-terraform init -no-color
-
-test-init-tf-root:
-  extends:
-    - .test-tf-root
-    - .versions
-  stage: test-init
-  script:
-    - export DEBUG_OUTPUT=true
-    - gitlab-terraform init
-
-test-init-tf-root-with-cd:
-  extends:
-    - .test-tf-root
-    - .versions
-  stage: test-init
-  script:
-    - cd tests
-    - export DEBUG_OUTPUT=true
-    - gitlab-terraform init
-
-test-init-tf-root-with-args:
-  extends:
-    - .test-tf-root
-    - .versions
-  stage: test-init
-  script:
-    - export DEBUG_OUTPUT=true
-    - gitlab-terraform init -get=true -no-color
-
-test-init-tf-root-with-flags:
-  extends:
-    - .test-tf-root
-    - .versions
-  stage: test-init
-  script:
-    - export DEBUG_OUTPUT=true
-    - export TF_INIT_FLAGS="-get=true -no-color"
-    - gitlab-terraform init
-
-test-init-tf-root-with-flags-and-args:
-  extends:
-    - .test-tf-root
-    - .versions
-  stage: test-init
-  script:
-    - export DEBUG_OUTPUT=true
-    - export TF_INIT_FLAGS="-get=true"
-    - gitlab-terraform init -no-color
-
-test-init-without-reconfigure:
-  extends:
-    - .test-tf-root
-    - .versions
-  stage: test-init
-  script:
-    - gitlab-terraform init
-    - |
-      cat <<EOF > $TF_ROOT/backend_override.tf
-      terraform {
-        backend "local" {}
-      }
-      EOF
-    - export TF_INIT_NO_RECONFIGURE=true
-    - FAILED=false
-    - gitlab-terraform init -no-color >/tmp/output.txt 2>&1 || FAILED=true
-    - cat /tmp/output.txt
-    - test $FAILED = true
-    - 'cat /tmp/output.txt | grep "Error: Backend configuration changed"'
-
-test-init-with-reconfigure:
-  extends:
-    - .test-tf-root
-    - .versions
-  stage: test-init
-  script:
-    - gitlab-terraform init
-    - |
-      cat <<EOF > $TF_ROOT/backend_override.tf
-      terraform {
-        backend "local" {}
-      }
-      EOF
-    - gitlab-terraform init
-
-test-init-with-prepared-registry-token:
-  extends:
-    - .test
-  stage: test-init
-  variables:
-    TERRAFORM_VERSION: $STABLE_VERSION
-  script:
-    - apk add --update $PKG
-    - |
-      cat <<'EOF' > test.sh
-      set -x
-      export TF_TOKEN_gitlab_com=mysecrettoken
-      . $(which gitlab-terraform)
-      terraform_authenticate_private_registry
-      test "$TF_TOKEN_gitlab_com" = "mysecrettoken"
-      EOF
-    - $SHELL test.sh
-  parallel:
-    matrix:
-      - SHELL: "bash"
-        PKG: "bash"
-      - SHELL: "zsh"
-        PKG: "zsh"
-      - SHELL: "ksh"
-        PKG: "loksh"
-
-test-init-without-prepared-registry-token:
-  extends:
-    - .test
-  stage: test-init
-  variables:
-    TERRAFORM_VERSION: $STABLE_VERSION
-  script:
-    - apk add --update $PKG
-    - |
-      cat <<'EOF' > test.sh
-      set -x
-      . $(which gitlab-terraform)
-      terraform_authenticate_private_registry
-      test -n "$TF_TOKEN_gitlab_com"
-      EOF
-    - $SHELL test.sh
-  parallel:
-    matrix:
-      - SHELL: "bash"
-        PKG: "bash"
-      - SHELL: "zsh"
-        PKG: "zsh"
-      - SHELL: "ksh"
-        PKG: "loksh"
-
-test-fmt:
-  extends:
-    - .test
-    - .versions
-  stage: test-fmt
-  script:
-    - gitlab-terraform fmt
-
-test-validate:
-  extends:
-    - .test
-    - .versions
-  stage: test-validate
-  script:
-    - gitlab-terraform validate
-
-test-plan:
-  extends:
-    - .test
-    - .versions
-  stage: test-plan
-  variables:
-    TF_PLAN_CACHE: $TERRAFORM_VERSION-plan.cache
-  script:
-    - gitlab-terraform plan
-    - if [[ ! -f "$TERRAFORM_VERSION-plan.cache" ]]; then echo "expected to find a plan.cache file"; exit 1; fi
-    - gitlab-terraform plan-json
-    - if [[ ! -f "plan.json" ]]; then echo "expected to find a plan.json file"; exit 1; fi
-  artifacts:
-    paths:
-      - "tests/*-plan.cache"
-
-test-apply:
-  extends:
-    - .test
-    - .versions
-  stage: test-apply
-  variables:
-    TF_PLAN_CACHE: $TERRAFORM_VERSION-plan.cache
-  script:
-    - gitlab-terraform apply
-
-test-destroy:
-  extends:
-    - .test
-    - .versions
-  stage: test-destroy
-  script:
-    - gitlab-terraform destroy
-
-test-source-script:
-  extends:
-    - .test
-  stage: test-misc
-  needs: [build]
-  variables:
-    TERRAFORM_VERSION: $STABLE_VERSION
-  before_script:
-    - !reference [.test-base, before_script]
-    - apk add --update $PKG
-  script:
-    - |
-      cat <<'EOF' > test.sh
-      set -x
-      test -z "$TF_GITLAB_SOURCED"
-      . $(which gitlab-terraform)
-      test $TF_GITLAB_SOURCED
-      EOF
-    - |
-      mkdir /usr/local/sbin
-      cat <<'EOF' > /usr/local/sbin/terraform
-      #/!usr/bin/env sh -e
-      echo "Called Terraform, but shouldn't have!!"
-      false
-      EOF
-      chmod +x /usr/local/sbin/terraform
-    - $SHELL test.sh
-  parallel:
-    matrix:
-      - SHELL: "bash"
-        PKG: "bash"
-      - SHELL: "zsh"
-        PKG: "zsh"
-      - SHELL: "ksh"
-        PKG: "loksh"
-
-test-without-implicit-init:
-  extends:
-    - .test
-  stage: test-misc
-  needs: [build]
-  cache:
-  variables:
-    TERRAFORM_VERSION: $STABLE_VERSION
-    STATE_NAME: $CI_JOB_NAME
-  script:
-    - export TF_IMPLICIT_INIT=false
-    - FAILED=false
-    - gitlab-terraform $CMD -no-color >/tmp/output.txt 2>&1 || FAILED=true
-    - cat /tmp/output.txt
-    - test $FAILED = true
-    - 'cat /tmp/output.txt | grep "$ERROR"'
-  parallel:
-    matrix:
-      - CMD: apply
-        ERROR: 'Error: Failed to load "plan.cache" as a plan'
-      - CMD: destroy
-        ERROR: 'Error: Backend initialization required, please run "terraform init"'
-      - CMD: plan
-        ERROR: 'Error: Backend initialization required, please run "terraform init"'
-      - CMD: validate
-        ERROR: 'Run "terraform init" to install all modules'
-
-test-no-wrapper:
-  extends:
-    - .test
-  stage: test-misc
-  needs: [build]
-  cache:
+release-terraform:
+  extends: .terraform-versions
+  stage: release
   variables:
-    TERRAFORM_VERSION: $STABLE_VERSION
-    STATE_NAME: $CI_JOB_NAME
+    RELEASE_IMAGE_NAME: "$CI_REGISTRY_IMAGE/releases/$TERRAFORM_VERSION"
+  image:
+    name: gcr.io/go-containerregistry/crane:debug
+    entrypoint: [""]
   script:
-    # NOTE: running `gitlab-terraform apply` wouldn't fail
-    #       because of the implicit `terraform init`.
-    - FAILED=false
-    - gitlab-terraform -- apply -no-color >/tmp/output.txt 2>&1 || FAILED=true
-    - cat /tmp/output.txt
-    - test $FAILED = true
-    - 'cat /tmp/output.txt | grep "Error: Backend initialization required, please run \"terraform init\""'
-
-integration-test-template:
-  stage: test-integration
-  variables:
-    TERRAFORM_VERSION: $STABLE_VERSION
-    TF_STATE_NAME: ci-integration-test-template-$CI_PIPELINE_IID-$CI_NODE_INDEX
-    TF_ROOT: tests
-  trigger:
-    include: .gitlab/integration-test/Test-$TEMPLATE
-    strategy: depend
+    # https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_copy.md
+    - crane auth login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
+    - crane copy "$TERRAFORM_IMAGE_NAME" "$RELEASE_IMAGE_NAME:latest"
+    - crane copy "$TERRAFORM_IMAGE_NAME" "$CI_REGISTRY_IMAGE/releases/terraform:$TERRAFORM_BINARY_VERSION"
+    - crane copy "$TERRAFORM_IMAGE_NAME" "$RELEASE_IMAGE_NAME:$CI_COMMIT_TAG"
+    - if [ "$TERRAFORM_VERSION" = "$STABLE_VERSION" ]; then crane copy "$TERRAFORM_IMAGE_NAME" "$STABLE_IMAGE_NAME"; fi
   rules:
-    - if: '$CI_PROJECT_PATH == "gitlab-org/terraform-images"'
-    - if: '$CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"'
-  parallel:
-    matrix:
-      - TEMPLATE: [Terraform.gitlab-ci.yml, Terraform.latest.gitlab-ci.yml]
+    - if: $CI_COMMIT_TAG
 
-release:
-  extends: .versions
+release-tofu:
+  extends: .tofu-versions
   stage: release
   image:
     name: gcr.io/go-containerregistry/crane:debug
     entrypoint: [""]
+  variables:
+    RELEASE_IMAGE_NAME: "$CI_REGISTRY_IMAGE/releases/tofu/$TOFU_VERSION"
   script:
     # https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_copy.md
     - crane auth login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
-    - crane copy "$BUILD_IMAGE_NAME" "$RELEASE_IMAGE_NAME:latest"
-    - crane copy "$BUILD_IMAGE_NAME" "$CI_REGISTRY_IMAGE/releases/terraform:$TERRAFORM_BINARY_VERSION"
-    - crane copy "$BUILD_IMAGE_NAME" "$RELEASE_IMAGE_NAME:$CI_COMMIT_TAG"
-    - if [ "$TERRAFORM_VERSION" = "$STABLE_VERSION" ]; then crane copy "$BUILD_IMAGE_NAME" "$STABLE_IMAGE_NAME"; fi
+    - crane copy "$TOFU_IMAGE_NAME" "$RELEASE_IMAGE_NAME:latest"
+    - crane copy "$TOFU_IMAGE_NAME" "$CI_REGISTRY_IMAGE/releases/tofu:$TOFU_BINARY_VERSION"
+    - crane copy "$TOFU_IMAGE_NAME" "$RELEASE_IMAGE_NAME:$CI_COMMIT_TAG"
+    # Removed the "stable" release for now, since tofu is still in beta itself
   rules:
     - if: $CI_COMMIT_TAG
 
diff --git a/.gitlab/integration-test/Test-Terraform.gitlab-ci.yml b/.gitlab/integration-test/Test-Terraform.gitlab-ci.yml
index 7a7b49c3a11b49f5e97c1cbfa30e8d99cd4ab3c3..be7adef5641674fde6375030febe8993d1f14a58 100644
--- a/.gitlab/integration-test/Test-Terraform.gitlab-ci.yml
+++ b/.gitlab/integration-test/Test-Terraform.gitlab-ci.yml
@@ -5,8 +5,7 @@ workflow:
 include:
   - template: Terraform.gitlab-ci.yml
 
-image: "$BUILD_IMAGE_NAME"
-
+image: "$IMAGE"
 
 # The `terraform apply` should always happen for the integration tests.
 # This prevents stalled manual pipelines, but more importantly tests
diff --git a/.gitlab/integration-test/Test-Terraform.latest.gitlab-ci.yml b/.gitlab/integration-test/Test-Terraform.latest.gitlab-ci.yml
index 7e7411047e8864d7aebc0c4a09ccd40e23ca5c55..8c2901c8dc3c47e6303fe3ac1893305be5755ade 100644
--- a/.gitlab/integration-test/Test-Terraform.latest.gitlab-ci.yml
+++ b/.gitlab/integration-test/Test-Terraform.latest.gitlab-ci.yml
@@ -6,7 +6,7 @@ include:
   - template: Terraform.latest.gitlab-ci.yml
 
 default:
-  image: "$BUILD_IMAGE_NAME"
+  image: "$IMAGE"
 
 .run-always:
   rules:
diff --git a/.gitlab/terraform-test.gitlab-ci.yml b/.gitlab/terraform-test.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3c82aeed066632d4bad347c7ac681ba43ee0ad6f
--- /dev/null
+++ b/.gitlab/terraform-test.gitlab-ci.yml
@@ -0,0 +1,342 @@
+.terraform-test-base:
+  image: "$TERRAFORM_IMAGE_NAME"
+  before_script:
+    - gitlab-terraform version
+    - jq --version
+  cache:
+    key: "$TERRAFORM_VERSION-$CI_COMMIT_REF_SLUG"
+    paths:
+      - tests/.terraform/
+
+.terraform-test:
+  extends:
+    - .terraform-test-base
+  before_script:
+    - !reference [.terraform-test-base, before_script]
+    - cd tests
+
+.test-tf-root:
+  extends:
+    - .terraform-test-base
+  variables:
+    TF_ROOT: tests
+
+terraform-test-init:
+  extends:
+    - .terraform-test
+    - .terraform-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init
+
+terraform-test-init-with-args:
+  extends:
+    - .terraform-test
+    - .terraform-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init -get=true -no-color
+
+terraform-test-init-with-flags:
+  extends:
+    - .terraform-test
+    - .terraform-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - export TF_INIT_FLAGS="-get=true -no-color"
+    - gitlab-terraform init
+
+terraform-test-init-with-flags-and-args:
+  extends:
+    - .terraform-test
+    - .terraform-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - export TF_INIT_FLAGS="-get=true"
+    - gitlab-terraform init -no-color
+
+terraform-test-init-tf-root:
+  extends:
+    - .test-tf-root
+    - .terraform-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init
+
+terraform-test-init-tf-root-with-cd:
+  extends:
+    - .test-tf-root
+    - .terraform-versions
+  stage: test-init
+  script:
+    - cd tests
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init
+
+terraform-test-init-tf-root-with-args:
+  extends:
+    - .test-tf-root
+    - .terraform-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init -get=true -no-color
+
+terraform-test-init-tf-root-with-flags:
+  extends:
+    - .test-tf-root
+    - .terraform-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - export TF_INIT_FLAGS="-get=true -no-color"
+    - gitlab-terraform init
+
+terraform-test-init-tf-root-with-flags-and-args:
+  extends:
+    - .test-tf-root
+    - .terraform-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - export TF_INIT_FLAGS="-get=true"
+    - gitlab-terraform init -no-color
+
+terraform-test-init-without-reconfigure:
+  extends:
+    - .test-tf-root
+    - .terraform-versions
+  stage: test-init
+  script:
+    - gitlab-terraform init
+    - |
+      cat <<EOF > $TF_ROOT/backend_override.tf
+      terraform {
+        backend "local" {}
+      }
+      EOF
+    - export TF_INIT_NO_RECONFIGURE=true
+    - FAILED=false
+    - gitlab-terraform init -no-color >/tmp/output.txt 2>&1 || FAILED=true
+    - cat /tmp/output.txt
+    - test $FAILED = true
+    - 'cat /tmp/output.txt | grep "Error: Backend configuration changed"'
+
+terraform-test-init-with-reconfigure:
+  extends:
+    - .test-tf-root
+    - .terraform-versions
+  stage: test-init
+  script:
+    - gitlab-terraform init
+    - |
+      cat <<EOF > $TF_ROOT/backend_override.tf
+      terraform {
+        backend "local" {}
+      }
+      EOF
+    - gitlab-terraform init
+
+terraform-test-init-with-prepared-registry-token:
+  extends:
+    - .terraform-test
+  stage: test-init
+  variables:
+    TERRAFORM_VERSION: $STABLE_VERSION
+  script:
+    - apk add --update $PKG
+    - |
+      cat <<'EOF' > test.sh
+      set -x
+      export TF_TOKEN_gitlab_com=mysecrettoken
+      . $(which gitlab-terraform)
+      terraform_authenticate_private_registry
+      test "$TF_TOKEN_gitlab_com" = "mysecrettoken"
+      EOF
+    - $SHELL test.sh
+  parallel:
+    matrix:
+      - SHELL: "bash"
+        PKG: "bash"
+      - SHELL: "zsh"
+        PKG: "zsh"
+      - SHELL: "ksh"
+        PKG: "loksh"
+
+terraform-test-init-without-prepared-registry-token:
+  extends:
+    - .terraform-test
+  stage: test-init
+  variables:
+    TERRAFORM_VERSION: $STABLE_VERSION
+  script:
+    - apk add --update $PKG
+    - |
+      cat <<'EOF' > test.sh
+      set -x
+      . $(which gitlab-terraform)
+      terraform_authenticate_private_registry
+      test -n "$TF_TOKEN_gitlab_com"
+      EOF
+    - $SHELL test.sh
+  parallel:
+    matrix:
+      - SHELL: "bash"
+        PKG: "bash"
+      - SHELL: "zsh"
+        PKG: "zsh"
+      - SHELL: "ksh"
+        PKG: "loksh"
+
+terraform-test-fmt:
+  extends:
+    - .terraform-test
+    - .terraform-versions
+  stage: test-fmt
+  script:
+    - gitlab-terraform fmt
+
+terraform-test-validate:
+  extends:
+    - .terraform-test
+    - .terraform-versions
+  stage: test-validate
+  script:
+    - gitlab-terraform validate
+
+terraform-test-plan:
+  extends:
+    - .terraform-test
+    - .terraform-versions
+  stage: test-plan
+  variables:
+    TF_PLAN_CACHE: $TERRAFORM_VERSION-plan.cache
+  script:
+    - gitlab-terraform plan
+    - if [[ ! -f "$TERRAFORM_VERSION-plan.cache" ]]; then echo "expected to find a plan.cache file"; exit 1; fi
+    - gitlab-terraform plan-json
+    - if [[ ! -f "plan.json" ]]; then echo "expected to find a plan.json file"; exit 1; fi
+  artifacts:
+    paths:
+      - "tests/*-plan.cache"
+
+terraform-test-apply:
+  extends:
+    - .terraform-test
+    - .terraform-versions
+  stage: test-apply
+  variables:
+    TF_PLAN_CACHE: $TERRAFORM_VERSION-plan.cache
+  script:
+    - gitlab-terraform apply
+
+terraform-test-destroy:
+  extends:
+    - .terraform-test
+    - .terraform-versions
+  stage: test-destroy
+  script:
+    - gitlab-terraform destroy
+
+terraform-test-source-script:
+  extends:
+    - .terraform-test
+  stage: test-misc
+  needs: [build terraform]
+  variables:
+    TERRAFORM_VERSION: $STABLE_VERSION
+  before_script:
+    - !reference [.terraform-test-base, before_script]
+    - apk add --update $PKG
+  script:
+    - |
+      cat <<'EOF' > test.sh
+      set -x
+      test -z "$TF_GITLAB_SOURCED"
+      . $(which gitlab-terraform)
+      test $TF_GITLAB_SOURCED
+      EOF
+    - |
+      mkdir /usr/local/sbin
+      cat <<'EOF' > /usr/local/sbin/terraform
+      #/!usr/bin/env sh -e
+      echo "Called Terraform, but shouldn't have!!"
+      false
+      EOF
+      chmod +x /usr/local/sbin/terraform
+    - $SHELL test.sh
+  parallel:
+    matrix:
+      - SHELL: "bash"
+        PKG: "bash"
+      - SHELL: "zsh"
+        PKG: "zsh"
+      - SHELL: "ksh"
+        PKG: "loksh"
+
+terraform-test-without-implicit-init:
+  extends:
+    - .terraform-test
+  stage: test-misc
+  needs: [build terraform]
+  cache:
+  variables:
+    TERRAFORM_VERSION: $STABLE_VERSION
+    STATE_NAME: $CI_JOB_NAME
+  script:
+    - export TF_IMPLICIT_INIT=false
+    - FAILED=false
+    - gitlab-terraform $CMD -no-color >/tmp/output.txt 2>&1 || FAILED=true
+    - cat /tmp/output.txt
+    - test $FAILED = true
+    - 'cat /tmp/output.txt | grep "$ERROR"'
+  parallel:
+    matrix:
+      - CMD: apply
+        ERROR: 'Error: Failed to load "plan.cache" as a plan'
+      - CMD: destroy
+        ERROR: 'Error: Backend initialization required, please run "terraform init"'
+      - CMD: plan
+        ERROR: 'Error: Backend initialization required, please run "terraform init"'
+      - CMD: validate
+        ERROR: 'Run "terraform init" to install all modules'
+
+terraform-test-no-wrapper:
+  extends:
+    - .terraform-test
+  stage: test-misc
+  needs: [build terraform]
+  cache:
+  variables:
+    TERRAFORM_VERSION: $STABLE_VERSION
+    STATE_NAME: $CI_JOB_NAME
+  script:
+    # NOTE: running `gitlab-terraform apply` wouldn't fail
+    #       because of the implicit `terraform init`.
+    - FAILED=false
+    - gitlab-terraform -- apply -no-color >/tmp/output.txt 2>&1 || FAILED=true
+    - cat /tmp/output.txt
+    - test $FAILED = true
+    - 'cat /tmp/output.txt | grep "Error: Backend initialization required, please run \"terraform init\""'
+
+terraform-integration-test-template:
+  stage: test-integration
+  variables:
+    IMAGE: $TERRAFORM_IMAGE_NAME
+    TERRAFORM_VERSION: $STABLE_VERSION
+    TF_STATE_NAME: ci-terraform-integration-test-template-$CI_PIPELINE_IID-$CI_NODE_INDEX
+    TF_ROOT: tests
+  trigger:
+    include: .gitlab/integration-test/Test-$TEMPLATE
+    strategy: depend
+  rules:
+    - if: '$CI_PROJECT_PATH == "gitlab-org/terraform-images"'
+    - if: '$CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"'
+  parallel:
+    matrix:
+      - TEMPLATE: [Terraform.gitlab-ci.yml, Terraform.latest.gitlab-ci.yml]
diff --git a/.gitlab/tofu-test.gitlab-ci.yml b/.gitlab/tofu-test.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3e9b2ae17b6c2887d678fa0dd2617e13c2d76909
--- /dev/null
+++ b/.gitlab/tofu-test.gitlab-ci.yml
@@ -0,0 +1,343 @@
+.tofu-test-base:
+  image: "$TOFU_IMAGE_NAME"
+  before_script:
+    - gitlab-terraform version
+    - jq --version
+  cache:
+    key: "$TOFU_VERSION-$CI_COMMIT_REF_SLUG"
+    paths:
+      - tests/.terraform/
+
+.tofu-test:
+  extends:
+    - .tofu-test-base
+  before_script:
+    - !reference [.tofu-test-base, before_script]
+    - cd tests
+
+.test-tofu-root:
+  extends:
+    - .tofu-test-base
+  variables:
+    TF_ROOT: tests
+
+tofu-test-init:
+  extends:
+    - .tofu-test
+    - .tofu-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init
+
+tofu-test-init-with-args:
+  extends:
+    - .tofu-test
+    - .tofu-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init -get=true -no-color
+
+tofu-test-init-with-flags:
+  extends:
+    - .tofu-test
+    - .tofu-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - export TF_INIT_FLAGS="-get=true -no-color"
+    - gitlab-terraform init
+
+tofu-test-init-with-flags-and-args:
+  extends:
+    - .tofu-test
+    - .tofu-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - export TF_INIT_FLAGS="-get=true"
+    - gitlab-terraform init -no-color
+
+tofu-test-init-tf-root:
+  extends:
+    - .test-tofu-root
+    - .tofu-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init
+
+tofu-test-init-tf-root-with-cd:
+  extends:
+    - .test-tofu-root
+    - .tofu-versions
+  stage: test-init
+  script:
+    - cd tests
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init
+
+tofu-test-init-tf-root-with-args:
+  extends:
+    - .test-tofu-root
+    - .tofu-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - gitlab-terraform init -get=true -no-color
+
+tofu-test-init-tf-root-with-flags:
+  extends:
+    - .test-tofu-root
+    - .tofu-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - export TF_INIT_FLAGS="-get=true -no-color"
+    - gitlab-terraform init
+
+tofu-test-init-tf-root-with-flags-and-args:
+  extends:
+    - .test-tofu-root
+    - .tofu-versions
+  stage: test-init
+  script:
+    - export DEBUG_OUTPUT=true
+    - export TF_INIT_FLAGS="-get=true"
+    - gitlab-terraform init -no-color
+
+tofu-test-init-without-reconfigure:
+  extends:
+    - .test-tofu-root
+    - .tofu-versions
+  stage: test-init
+  script:
+    - gitlab-terraform init
+    - |
+      cat <<EOF > $TF_ROOT/backend_override.tf
+      terraform {
+        backend "local" {}
+      }
+      EOF
+    - export TF_INIT_NO_RECONFIGURE=true
+    - FAILED=false
+    - gitlab-terraform init -no-color >/tmp/output.txt 2>&1 || FAILED=true
+    - cat /tmp/output.txt
+    - test $FAILED = true
+    - 'cat /tmp/output.txt | grep "Error: Backend configuration changed"'
+
+tofu-test-init-with-reconfigure:
+  extends:
+    - .test-tofu-root
+    - .tofu-versions
+  stage: test-init
+  script:
+    - gitlab-terraform init
+    - |
+      cat <<EOF > $TF_ROOT/backend_override.tf
+      terraform {
+        backend "local" {}
+      }
+      EOF
+    - gitlab-terraform init
+
+tofu-test-init-with-prepared-registry-token:
+  extends:
+    - .tofu-test
+  stage: test-init
+  variables:
+    TOFU_VERSION: $STABLE_TOFU_VERSION
+  script:
+    - apk add --update $PKG
+    - |
+      cat <<'EOF' > test.sh
+      set -x
+      export TF_TOKEN_gitlab_com=mysecrettoken
+      . $(which gitlab-terraform)
+      terraform_authenticate_private_registry
+      test "$TF_TOKEN_gitlab_com" = "mysecrettoken"
+      EOF
+    - $SHELL test.sh
+  parallel:
+    matrix:
+      - SHELL: "bash"
+        PKG: "bash"
+      - SHELL: "zsh"
+        PKG: "zsh"
+      - SHELL: "ksh"
+        PKG: "loksh"
+
+tofu-test-init-without-prepared-registry-token:
+  extends:
+    - .tofu-test
+  stage: test-init
+  variables:
+    TOFU_VERSION: $STABLE_TOFU_VERSION
+  script:
+    - apk add --update $PKG
+    - |
+      cat <<'EOF' > test.sh
+      set -x
+      . $(which gitlab-terraform)
+      terraform_authenticate_private_registry
+      test -n "$TF_TOKEN_gitlab_com"
+      EOF
+    - $SHELL test.sh
+  parallel:
+    matrix:
+      - SHELL: "bash"
+        PKG: "bash"
+      - SHELL: "zsh"
+        PKG: "zsh"
+      - SHELL: "ksh"
+        PKG: "loksh"
+
+tofu-test-fmt:
+  extends:
+    - .tofu-test
+    - .tofu-versions
+  stage: test-fmt
+  script:
+    - gitlab-terraform fmt
+
+tofu-test-validate:
+  extends:
+    - .tofu-test
+    - .tofu-versions
+  stage: test-validate
+  script:
+    - gitlab-terraform validate
+
+tofu-test-plan:
+  extends:
+    - .tofu-test
+    - .tofu-versions
+  stage: test-plan
+  variables:
+    TF_PLAN_CACHE: $TOFU_VERSION-plan.cache
+  script:
+    - gitlab-terraform plan
+    - if [[ ! -f "$TOFU_VERSION-plan.cache" ]]; then echo "expected to find a plan.cache file"; exit 1; fi
+    - gitlab-terraform plan-json
+    - if [[ ! -f "plan.json" ]]; then echo "expected to find a plan.json file"; exit 1; fi
+  artifacts:
+    paths:
+      - "tests/*-plan.cache"
+
+tofu-test-apply:
+  extends:
+    - .tofu-test
+    - .tofu-versions
+  stage: test-apply
+  variables:
+    TF_PLAN_CACHE: $TOFU_VERSION-plan.cache
+  script:
+    - gitlab-terraform apply
+
+tofu-test-destroy:
+  extends:
+    - .tofu-test
+    - .tofu-versions
+  stage: test-destroy
+  script:
+    - gitlab-terraform destroy
+
+tofu-test-source-script:
+  extends:
+    - .tofu-test
+  stage: test-misc
+  needs: [build tofu]
+  variables:
+    TOFU_VERSION: $STABLE_TOFU_VERSION
+  before_script:
+    - !reference [.tofu-test-base, before_script]
+    - apk add --update $PKG
+  script:
+    - |
+      cat <<'EOF' > test.sh
+      set -x
+      test -z "$TF_GITLAB_SOURCED"
+      . $(which gitlab-terraform)
+      test $TF_GITLAB_SOURCED
+      EOF
+    - |
+      mkdir /usr/local/sbin
+      cat <<'EOF' > /usr/local/sbin/terraform
+      #/!usr/bin/env sh -e
+      echo "Called Terraform, but shouldn't have!!"
+      false
+      EOF
+      chmod +x /usr/local/sbin/terraform
+    - $SHELL test.sh
+  parallel:
+    matrix:
+      - SHELL: "bash"
+        PKG: "bash"
+      - SHELL: "zsh"
+        PKG: "zsh"
+      - SHELL: "ksh"
+        PKG: "loksh"
+
+tofu-test-without-implicit-init:
+  extends:
+    - .tofu-test
+  stage: test-misc
+  needs: [build tofu]
+  cache:
+  variables:
+    TOFU_VERSION: $STABLE_TOFU_VERSION
+    STATE_NAME: $CI_JOB_NAME
+  script:
+    - export TF_IMPLICIT_INIT=false
+    - FAILED=false
+    - gitlab-terraform $CMD -no-color >/tmp/output.txt 2>&1 || FAILED=true
+    - cat /tmp/output.txt
+    - test $FAILED = true
+    - 'cat /tmp/output.txt | grep "$ERROR"'
+  parallel:
+    matrix:
+      - CMD: apply
+        ERROR: 'Error: Failed to load "plan.cache" as a plan'
+      - CMD: destroy
+        ERROR: 'Error: Backend initialization required, please run "tofu init"'
+      - CMD: plan
+        ERROR: 'Error: Backend initialization required, please run "tofu init"'
+      - CMD: validate
+        ERROR: 'Run "tofu init" to install all modules'
+
+tofu-test-no-wrapper:
+  extends:
+    - .tofu-test
+  stage: test-misc
+  needs: [build tofu]
+  cache:
+  variables:
+    TOFU_VERSION: $STABLE_TOFU_VERSION
+    STATE_NAME: $CI_JOB_NAME
+  script:
+    # NOTE: running `gitlab-terraform apply` wouldn't fail
+    #       because of the implicit `terraform init`.
+    - FAILED=false
+    - gitlab-terraform -- apply -no-color >/tmp/output.txt 2>&1 || FAILED=true
+    - cat /tmp/output.txt
+    - test $FAILED = true
+    - 'cat /tmp/output.txt | grep "Error: Backend initialization required, please run \"tofu init\""'
+
+tofu-integration-test-template:
+  stage: test-integration
+  variables:
+    IMAGE: $TOFU_IMAGE_NAME
+    # Hard-coded for now since there's no tofu stable version yet
+    TOFU_VERSION: "1.6"
+    TF_STATE_NAME: ci-tofu-integration-test-template-$CI_PIPELINE_IID-$CI_NODE_INDEX
+    TF_ROOT: tests
+  trigger:
+    include: .gitlab/integration-test/Test-$TEMPLATE
+    strategy: depend
+  rules:
+    - if: '$CI_PROJECT_PATH == "gitlab-org/terraform-images"'
+    - if: '$CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"'
+  parallel:
+    matrix:
+      - TEMPLATE: [Terraform.gitlab-ci.yml, Terraform.latest.gitlab-ci.yml]
diff --git a/Dockerfile b/Dockerfile.terraform
similarity index 100%
rename from Dockerfile
rename to Dockerfile.terraform
diff --git a/Dockerfile.tofu b/Dockerfile.tofu
new file mode 100644
index 0000000000000000000000000000000000000000..ae7943cdba348fe0293d030b43f7f880c2a98175
--- /dev/null
+++ b/Dockerfile.tofu
@@ -0,0 +1,28 @@
+ARG BASE_IMAGE
+
+FROM $BASE_IMAGE
+
+ARG TOFU_BINARY_VERSION
+
+RUN apk add --no-cache \
+  curl \
+  gcompat \
+  git \
+  idn2-utils \
+  jq \
+  openssh
+
+
+# Install tofu from the alpine registry and symlink to terraform (for the utility script)
+RUN apk add --no-cache opentofu=$TOFU_BINARY_VERSION --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing/ && \
+    ln -s "$(which tofu)" /usr/local/bin/terraform && \
+    terraform --version && \
+    tofu --version
+
+WORKDIR /
+
+COPY src/bin/gitlab-terraform.sh /usr/bin/gitlab-terraform
+RUN chmod +x /usr/bin/gitlab-terraform
+
+# Override ENTRYPOINT
+ENTRYPOINT []
diff --git a/src/bin/gitlab-terraform.sh b/src/bin/gitlab-terraform.sh
index 79056b437623b200e5b28caf2314542767c15bbb..380e561f1131f2d05196da29728489d63fcb397d 100755
--- a/src/bin/gitlab-terraform.sh
+++ b/src/bin/gitlab-terraform.sh
@@ -5,11 +5,20 @@ if [ "${DEBUG_OUTPUT}" = "true" ]; then
 fi
 
 # Helpers
+# Check if the terraform version is greater than the input argument
 terraform_is_at_least() {
   [ "${1}" = "$(terraform -version | awk -v min="${1}" '/^Terraform v/{ sub(/^v/, "", $2); print min; print $2 }' | sort -V | head -n1)" ]
   return $?
 }
 
+# Check if the script is using OpenTofu or Terraform, returns 0
+# if tofu is present, 1 if it's not
+using_open_tofu() {
+  # If tofu is on the path, we're using tofu, not terraform.
+  [ "$(which tofu)" ]
+  return $?
+}
+
 # Evaluate if this script is being sourced or executed directly.
 # See https://stackoverflow.com/a/28776166
 sourced=0
@@ -103,8 +112,8 @@ fi
 
 
 terraform_authenticate_private_registry() {
-  if terraform_is_at_least 1.2.0; then
-    # From Terraform 1.2.0 and later, we can use TF_TOKEN_your_domain_name to authenticate to registry.
+  if terraform_is_at_least 1.2.0 || using_open_tofu ; then
+    # From Terraform 1.2.0 and later (or all versions of OpenTofu), we can use TF_TOKEN_your_domain_name to authenticate to registry.
     # The credential environment variable has the following requirements:
     # - Domain names containing non-ASCII characters are converted to their punycode equivalent with an ACE prefix
     # - Periods are encoded as underscores