diff --git a/.gitlab/README.md.template b/.gitlab/README.md.template
index c2556569b66201891dee8ccc0b5ad1b5d33c8172..6addbf731d21190384674b2752e7f81451756b25 100644
--- a/.gitlab/README.md.template
+++ b/.gitlab/README.md.template
@@ -138,6 +138,50 @@ terraform {
 We recommend having a dedicated `backend.tf` file inside your `root_dir`
 with the aforementioned block.
 
+### State and Plan Encryption
+
+We recommend that you configure the OpenTofu
+[State and Plan Encryption](https://opentofu.org/docs/language/state/encryption).
+
+You may either do this manually by commit your `encryption` config and providing
+it with the necessary secrets - for example defining a `sensitive` `variable`
+and configure a GitLab CI/CD variable for it.
+
+Another option is to let this component auto-encrypt the state and plan for you.
+The only thing you have to do is to provide a passphrase.
+
+All templates related to the state have the following inputs related to auto-encryption:
+
+- `auto_encrpytion` (`boolean`): if set to `true` will auto-encrypt your state and plan.
+- `auto_encrpytion_passphrase` (`string`): is required if `auto_encrpytion` is `true` and
+  defines the passphrase for your state and plan files. Make sure to keep it secured.
+  You may use a protected and masked GitLab CI/CD variable for it.
+- `auto_encryption_enable_migration_from_unencrypted` (`boolean`): if set to `true` will
+  migrate automatically migrate an unencrypted state and plan into an encrypted one.
+  This should only be set to `true` temporarily and disabled again afterwards.
+  Currently, a migration to an encrypted state requires actual changes to the
+  infrastructure.
+  See [this comment](https://gitlab.com/gitlab-org/gitlab/-/issues/450816#note_2228897756)
+  for details.
+
+The following snippet will auto-encrypt your state with a passphrase coming from the
+`PASSPHRASE` CI/CD variable:
+
+```yaml
+include:
+  - component: $CI_SERVER_FQDN/components/opentofu/validate-plan-apply@<VERSION>
+    inputs:
+      # The version must currently be specified explicitly as an input,
+      # to find the correctly associated images. # This can be removed
+      # once https://gitlab.com/gitlab-org/gitlab/-/issues/438275 is solved.
+      version: <VERSION> # component version
+      opentofu_version: <OPENTOFU_VERSION>
+      auto_encrpytion: true
+      auto_encrpytion_passphrase: $PASSPHRASE
+
+stages: [validate, build, deploy]
+```
+
 ### Access to Terraform Module Registry
 
 Similar to automatically configuring the [GitLab-managed Terraform state backend]
@@ -326,96 +370,6 @@ or `TF_CLI_ARGS_init` (handled by OpenTofu directly) to `-lockfile=readonly`
 to prevent any changes to the lockfile during the pipeline job and with
 that ensuring that OpenTofu really uses the locked dependencies.
 
-#### State and Plan Encryption
-
-We recommend that you configure the OpenTofu
-[State and Plan Encryption](https://opentofu.org/docs/language/state/encryption).
-
-You can easily do this by following the guide on the page linked above.
-
-Here is an example:
-
-**Tofu config at `<root-dir>/encryption.tf`**:
-
-```hcl
-variable "passphrase" {
-  sensitive   = true
-  description = "Passphrase to encrypt and decrypt state and plan"
-}
-
-terraform {
-  encryption {
-    key_provider "pbkdf2" "this" {
-      passphrase = var.passphrase
-    }
-
-    method "aes_gcm" "this" {
-      keys = key_provider.pbkdf2.this
-    }
-
-    state {
-      method = method.aes_gcm.this
-    }
-
-    plan {
-      method = method.aes_gcm.this
-    }
-  }
-}
-```
-
-Then you only have to configure a passphrase as CI/CD variable with the name
-`TF_VAR_passphrase`.
-
-Everything else will work out of the box.
-
-In case you want to migrate from an unencrypted state and plan you can
-temporarily configure your encryption block with `fallback`s, like so:
-
-```hcl
-variable "passphrase" {
-  sensitive   = true
-  description = "Passphrase to ecnrypt and decrypt state and plan"
-}
-
-terraform {
-  encryption {
-    method "unencrypted" "migrate" {}
-
-    key_provider "pbkdf2" "this" {
-      passphrase = var.passphrase
-    }
-
-    method "aes_gcm" "this" {
-      keys = key_provider.pbkdf2.this
-    }
-
-    state {
-      method = method.aes_gcm.this
-
-      fallback {
-        method = method.unencrypted.migrate
-      }
-    }
-
-    plan {
-      method = method.aes_gcm.this
-
-      fallback {
-        method = method.unencrypted.migrate
-      }
-    }
-  }
-}
-```
-
-Then you can run the pipeline one time to migrate and then remove the
-`unencrypted` `method` and the `fallback` blocks.
-
-> **Call for Action**:
-> If you have a good proposal on how to make state and plan encryption
-> easier with this component then let us know in an issue!
-
 ### Examples
 
 Here are some example repositories to demonstrate how this component maybe used:
diff --git a/README.md b/README.md
index f3e7b37e3fff3579c9d05d85232025fdb2c66e6b..11123be4957ee20498f71fcc649aaffa10901c34 100644
--- a/README.md
+++ b/README.md
@@ -140,6 +140,50 @@ terraform {
 We recommend having a dedicated `backend.tf` file inside your `root_dir`
 with the aforementioned block.
 
+### State and Plan Encryption
+
+We recommend that you configure the OpenTofu
+[State and Plan Encryption](https://opentofu.org/docs/language/state/encryption).
+
+You may either do this manually by commit your `encryption` config and providing
+it with the necessary secrets - for example defining a `sensitive` `variable`
+and configure a GitLab CI/CD variable for it.
+
+Another option is to let this component auto-encrypt the state and plan for you.
+The only thing you have to do is to provide a passphrase.
+
+All templates related to the state have the following inputs related to auto-encryption:
+
+- `auto_encrpytion` (`boolean`): if set to `true` will auto-encrypt your state and plan.
+- `auto_encrpytion_passphrase` (`string`): is required if `auto_encrpytion` is `true` and
+  defines the passphrase for your state and plan files. Make sure to keep it secured.
+  You may use a protected and masked GitLab CI/CD variable for it.
+- `auto_encryption_enable_migration_from_unencrypted` (`boolean`): if set to `true` will
+  migrate automatically migrate an unencrypted state and plan into an encrypted one.
+  This should only be set to `true` temporarily and disabled again afterwards.
+  Currently, a migration to an encrypted state requires actual changes to the
+  infrastructure.
+  See [this comment](https://gitlab.com/gitlab-org/gitlab/-/issues/450816#note_2228897756)
+  for details.
+
+The following snippet will auto-encrypt your state with a passphrase coming from the
+`PASSPHRASE` CI/CD variable:
+
+```yaml
+include:
+  - component: $CI_SERVER_FQDN/components/opentofu/validate-plan-apply@<VERSION>
+    inputs:
+      # The version must currently be specified explicitly as an input,
+      # to find the correctly associated images. # This can be removed
+      # once https://gitlab.com/gitlab-org/gitlab/-/issues/438275 is solved.
+      version: <VERSION> # component version
+      opentofu_version: <OPENTOFU_VERSION>
+      auto_encrpytion: true
+      auto_encrpytion_passphrase: $PASSPHRASE
+
+stages: [validate, build, deploy]
+```
+
 ### Access to Terraform Module Registry
 
 Similar to automatically configuring the [GitLab-managed Terraform state backend]
@@ -274,6 +318,9 @@ The following environment variables are respected by the `gitlab-tofu` script:
 - `GITLAB_TOFU_USE_DETAILED_EXITCODE`: if set to true, `-detailed-exitcode` is supplied to `tofu plan`. Defaults to `false`.
 - `GITLAB_TOFU_PLAN_WITH_JSON`: if set to true, will directly generate a JSON plan file when running `gitlab-tofu plan`. Defaults to `false`.
 - `GITLAB_TOFU_VAR_FILE`: if set to a path it will pass `-var-file` to all `tofu` commands that support it.
+- `GITLAB_TOFU_AUTO_ENCRYPTION`: if set to true, enables auto state and plan encryption. Defaults to `false`.
+- `GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE`: the passphrase to use for state and plan encryption. Required if `GITLAB_TOFU_AUTO_ENCRYPTION` is true.
+- `GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED_ENABLED`: if set to true, enables a fallback for state and plan encryption to migrate unencrypted plans and states to encrypted ones. Defaults to `false`.
 
 #### Respected OpenTofu Environment Variables
 
@@ -402,96 +449,6 @@ or `TF_CLI_ARGS_init` (handled by OpenTofu directly) to `-lockfile=readonly`
 to prevent any changes to the lockfile during the pipeline job and with
 that ensuring that OpenTofu really uses the locked dependencies.
 
-#### State and Plan Encryption
-
-We recommend that you configure the OpenTofu
-[State and Plan Encryption](https://opentofu.org/docs/language/state/encryption).
-
-You can easily do this by following the guide on the page linked above.
-
-Here is an example:
-
-**Tofu config at `<root-dir>/encryption.tf`**:
-
-```hcl
-variable "passphrase" {
-  sensitive   = true
-  description = "Passphrase to encrypt and decrypt state and plan"
-}
-
-terraform {
-  encryption {
-    key_provider "pbkdf2" "this" {
-      passphrase = var.passphrase
-    }
-
-    method "aes_gcm" "this" {
-      keys = key_provider.pbkdf2.this
-    }
-
-    state {
-      method = method.aes_gcm.this
-    }
-
-    plan {
-      method = method.aes_gcm.this
-    }
-  }
-}
-```
-
-Then you only have to configure a passphrase as CI/CD variable with the name
-`TF_VAR_passphrase`.
-
-Everything else will work out of the box.
-
-In case you want to migrate from an unencrypted state and plan you can
-temporarily configure your encryption block with `fallback`s, like so:
-
-```hcl
-variable "passphrase" {
-  sensitive   = true
-  description = "Passphrase to ecnrypt and decrypt state and plan"
-}
-
-terraform {
-  encryption {
-    method "unencrypted" "migrate" {}
-
-    key_provider "pbkdf2" "this" {
-      passphrase = var.passphrase
-    }
-
-    method "aes_gcm" "this" {
-      keys = key_provider.pbkdf2.this
-    }
-
-    state {
-      method = method.aes_gcm.this
-
-      fallback {
-        method = method.unencrypted.migrate
-      }
-    }
-
-    plan {
-      method = method.aes_gcm.this
-
-      fallback {
-        method = method.unencrypted.migrate
-      }
-    }
-  }
-}
-```
-
-Then you can run the pipeline one time to migrate and then remove the
-`unencrypted` `method` and the `fallback` blocks.
-
-> **Call for Action**:
-> If you have a good proposal on how to make state and plan encryption
-> easier with this component then let us know in an issue!
-
 ### Examples
 
 Here are some example repositories to demonstrate how this component maybe used:
diff --git a/src/gitlab-tofu.sh b/src/gitlab-tofu.sh
index 5c33e2b6f3f91e29c4e592b1196b4391b9fc6080..4b98d618f4a3d9deba7cf0eaa8d0535797a34e18 100644
--- a/src/gitlab-tofu.sh
+++ b/src/gitlab-tofu.sh
@@ -27,6 +27,9 @@
 # - `GITLAB_TOFU_USE_DETAILED_EXITCODE`: if set to true, `-detailed-exitcode` is supplied to `tofu plan`. Defaults to `false`.
 # - `GITLAB_TOFU_PLAN_WITH_JSON`: if set to true, will directly generate a JSON plan file when running `gitlab-tofu plan`. Defaults to `false`.
 # - `GITLAB_TOFU_VAR_FILE`: if set to a path it will pass `-var-file` to all `tofu` commands that support it.
+# - `GITLAB_TOFU_AUTO_ENCRYPTION`: if set to true, enables auto state and plan encryption. Defaults to `false`.
+# - `GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE`: the passphrase to use for state and plan encryption. Required if `GITLAB_TOFU_AUTO_ENCRYPTION` is true.
+# - `GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED_ENABLED`: if set to true, enables a fallback for state and plan encryption to migrate unencrypted plans and states to encrypted ones. Defaults to `false`.
 #
 # #### Respected OpenTofu Environment Variables
 #
@@ -197,6 +200,21 @@ plan_jq_filter='
   }
 '
 
+# auto encryption related variables
+auto_encryption_enabled=${GITLAB_TOFU_AUTO_ENCRYPTION:-false}
+auto_encryption_passphrase="${GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE}"
+auto_encryption_migration_from_unencrypted_enabled=${GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED:-false}
+if $auto_encryption_enabled && [ -z "${auto_encryption_passphrase}" ]; then
+  # shellcheck disable=SC2016 # we actually want to print the variable names and not expand them.
+  echo 'ERROR: a passphrase ($GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE) must be provided when enabled auto encryption ($GITLAB_TOFU_AUTO_ENCRYPTION)' >&2
+  exit 1
+fi
+if ! $auto_encryption_enabled && $auto_encryption_migration_from_unencrypted_enabled; then
+  # shellcheck disable=SC2016 # we actually want to print the variable names and not expand them.
+  echo 'ERROR: you must enable auto encryption ($GITLAB_TOFU_AUTO_ENCRYPTION) to enable migration from an unencrypted state ($GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED)' >&2
+  exit 1
+fi
+
 # Misc variables
 var_file="${GITLAB_TOFU_VAR_FILE}"
 
@@ -268,8 +286,69 @@ tofu_init() {
     1>&2 || $should_ignore_init_errors
 }
 
+
+# configure_encryption_for_tofu configures automatic state and plan encryption
+configure_encryption_for_tofu() {
+  if ! $auto_encryption_enabled; then
+    return
+  fi
+
+  if $auto_encryption_migration_from_unencrypted_enabled; then
+    TF_ENCRYPTION=$(cat <<EOF
+method "unencrypted" "gitlab_tofu_auto_encryption_migrate" {}
+
+key_provider "pbkdf2" "gitlab_tofu_auto_encryption" {
+  passphrase = "${auto_encryption_passphrase}"
+}
+
+method "aes_gcm" "gitlab_tofu_auto_encryption" {
+  keys = key_provider.pbkdf2.gitlab_tofu_auto_encryption
+}
+
+state {
+  method = method.aes_gcm.gitlab_tofu_auto_encryption
+
+  fallback {
+    method = method.unencrypted.gitlab_tofu_auto_encryption_migrate
+  }
+}
+
+plan {
+  method = method.aes_gcm.gitlab_tofu_auto_encryption
+
+  fallback {
+    method = method.unencrypted.gitlab_tofu_auto_encryption_migrate
+  }
+}
+EOF
+)
+  else
+    TF_ENCRYPTION=$(cat <<EOF
+key_provider "pbkdf2" "gitlab_tofu_auto_encryption" {
+  passphrase = "${auto_encryption_passphrase}"
+}
+
+method "aes_gcm" "gitlab_tofu_auto_encryption" {
+  keys = key_provider.pbkdf2.gitlab_tofu_auto_encryption
+}
+
+state {
+  method = method.aes_gcm.gitlab_tofu_auto_encryption
+}
+
+plan {
+  method = method.aes_gcm.gitlab_tofu_auto_encryption
+}
+EOF
+)
+  fi
+
+  export TF_ENCRYPTION
+}
+
 # We always want to configure the tofu variables, even in source-mode.
 configure_variables_for_tofu
+configure_encryption_for_tofu
 
 # If this script is executed and not sourced, a tofu command is ran.
 # Otherwise, nothing happens and the sourced shell can use the defined variables
diff --git a/templates/apply.yml b/templates/apply.yml
index af617ec685761fc7416b1f67d9a76c740ae54019..cbc4df9a21d89a422869a07159aa454b0045e41e 100644
--- a/templates/apply.yml
+++ b/templates/apply.yml
@@ -92,6 +92,18 @@ spec:
       default: pull-push
       type: string
       description: 'Defines the cache policy of the job.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -115,6 +127,9 @@ spec:
     GITLAB_TOFU_APPLY_NO_PLAN: $[[ inputs.no_plan ]]
     GITLAB_TOFU_PLAN_NAME: $[[ inputs.plan_name ]]
     GITLAB_TOFU_VAR_FILE: '$[[ inputs.var_file ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/destroy.yml b/templates/destroy.yml
index 0e1fb04ecc29d393793c1ed732ab150b4d2b1b77..7c159619fc7a309492713faa71d8fe41d4e619aa 100644
--- a/templates/destroy.yml
+++ b/templates/destroy.yml
@@ -92,6 +92,18 @@ spec:
       default: pull-push
       type: string
       description: 'Defines the cache policy of the job.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -115,6 +127,9 @@ spec:
     GITLAB_TOFU_APPLY_NO_PLAN: $[[ inputs.no_plan ]]
     GITLAB_TOFU_PLAN_NAME: $[[ inputs.plan_name ]]
     GITLAB_TOFU_VAR_FILE: '$[[ inputs.var_file ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/full-pipeline.yml b/templates/full-pipeline.yml
index 9b83944d2525fd611117c0f6cc177ec51aaab2c0..815c43c8ec041b7b0f0d338b04abdaf8417e5bc4 100644
--- a/templates/full-pipeline.yml
+++ b/templates/full-pipeline.yml
@@ -188,6 +188,18 @@ spec:
       default: [{when: on_success}]
       type: array
       description: 'Defines the `rules` of the child pipeline bridge job.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -223,6 +235,9 @@ include:
       state_name: $[[ inputs.state_name ]]
       var_file: $[[ inputs.var_file ]]
       rules: $[[ inputs.validate_rules ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/test.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "true"'
@@ -243,6 +258,9 @@ include:
       var_file: $[[ inputs.var_file ]]
       needs: []
       rules: $[[ inputs.test_rules ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/plan.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -262,6 +280,9 @@ include:
       var_file: $[[ inputs.var_file ]]
       rules: $[[ inputs.plan_rules ]]
       warning_on_non_empty_plan: $[[ inputs.warning_on_non_empty_plan ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/apply.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -279,6 +300,9 @@ include:
       plan_name: $[[ inputs.plan_name ]]
       var_file: $[[ inputs.var_file ]]
       rules: $[[ inputs.apply_rules ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/destroy.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -295,6 +319,9 @@ include:
       state_name: $[[ inputs.state_name ]]
       var_file: $[[ inputs.var_file ]]
       rules: $[[ inputs.destroy_rules ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/delete-state.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -378,6 +405,9 @@ stages:
           destroy_rules: $[[ inputs.destroy_rules ]]
           delete_state_rules: $[[ inputs.delete_state_rules ]]
           warning_on_non_empty_plan: $[[ inputs.warning_on_non_empty_plan ]]
+          auto_encryption: $[[ inputs.auto_encryption ]]
+          auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+          auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
           trigger_in_child_pipeline: false
     forward:
       yaml_variables: true
diff --git a/templates/graph.yml b/templates/graph.yml
index 561d44ee074660c77a3b16ae474dd837aaa2f8d5..e8dd1ba0105dff37385d1582c9b461aa6fed905f 100644
--- a/templates/graph.yml
+++ b/templates/graph.yml
@@ -90,6 +90,18 @@ spec:
       default: pull-push
       type: string
       description: 'Defines the cache policy of the job.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -107,6 +119,9 @@ spec:
     GITLAB_TOFU_ROOT_DIR: $[[ inputs.root_dir ]]
     GITLAB_TOFU_STATE_NAME: $[[ inputs.state_name ]]
     GITLAB_TOFU_VAR_FILE: '$[[ inputs.var_file ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/job-templates.yml b/templates/job-templates.yml
index 6e7aa58cb3401421d6f15b3c94cd17504c3008c8..eda3791a09397e1a2658675820bbf638cf623dec 100644
--- a/templates/job-templates.yml
+++ b/templates/job-templates.yml
@@ -93,6 +93,18 @@ spec:
       default: false
       type: boolean
       description: 'Whether to mark the job with a warning if the plan contains a diff.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -121,6 +133,9 @@ include:
       root_dir: $[[ inputs.root_dir ]]
       state_name: $[[ inputs.state_name ]]
       var_file: $[[ inputs.var_file ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/graph.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]graph'
@@ -132,6 +147,9 @@ include:
       image_name: $[[ inputs.image_name ]]
       root_dir: $[[ inputs.root_dir ]]
       var_file: $[[ inputs.var_file ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/test.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]test'
@@ -145,6 +163,9 @@ include:
       root_dir: $[[ inputs.root_dir ]]
       state_name: $[[ inputs.state_name ]]
       var_file: $[[ inputs.var_file ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/plan.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]plan'
@@ -160,6 +181,9 @@ include:
       plan_name: $[[ inputs.plan_name ]]
       var_file: $[[ inputs.var_file ]]
       warning_on_non_empty_plan: $[[ inputs.warning_on_non_empty_plan ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/apply.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]apply'
@@ -174,6 +198,9 @@ include:
       state_name: $[[ inputs.state_name ]]
       plan_name: $[[ inputs.plan_name ]]
       var_file: $[[ inputs.var_file ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/destroy.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]destroy'
@@ -188,6 +215,9 @@ include:
       state_name: $[[ inputs.state_name ]]
       plan_name: $[[ inputs.plan_name ]]
       var_file: $[[ inputs.var_file ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/delete-state.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]delete-state'
diff --git a/templates/plan.yml b/templates/plan.yml
index 85710bec4e3a787999ea45a8f93f6162aad04b71..a0d3235b3504e3ce90a9a92c4fea4cda0831c175 100644
--- a/templates/plan.yml
+++ b/templates/plan.yml
@@ -99,6 +99,18 @@ spec:
       default: false
       type: boolean
       description: 'Whether to mark the job with a warning if the plan contains a diff.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -161,6 +173,9 @@ spec:
     GITLAB_TOFU_PLAN_NAME: $[[ inputs.plan_name ]]
     GITLAB_TOFU_PLAN_WITH_JSON: true
     GITLAB_TOFU_VAR_FILE: '$[[ inputs.var_file ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/test.yml b/templates/test.yml
index 7634385f54c2b360ed8c9032e240b400fd42f84a..2de8cee0787a2c77d1f8e9b060681518396ee196 100644
--- a/templates/test.yml
+++ b/templates/test.yml
@@ -92,6 +92,18 @@ spec:
       default: pull-push
       type: string
       description: 'Defines the cache policy of the job.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -110,6 +122,9 @@ spec:
     GITLAB_TOFU_ROOT_DIR: $[[ inputs.root_dir ]]
     GITLAB_TOFU_STATE_NAME: $[[ inputs.state_name ]]
     GITLAB_TOFU_VAR_FILE: '$[[ inputs.var_file ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/validate-plan-apply.yml b/templates/validate-plan-apply.yml
index 1e8aad05cfe3042c4dab76baf06ba6cc0ec8df60..c74ee0a7154a03de36329465a251e7572ab14f96 100644
--- a/templates/validate-plan-apply.yml
+++ b/templates/validate-plan-apply.yml
@@ -152,6 +152,18 @@ spec:
       default: [{when: on_success}]
       type: array
       description: 'Defines the `rules` of the child pipeline bridge job.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -188,6 +200,9 @@ include:
       var_file: $[[ inputs.var_file ]]
       rules: $[[ inputs.validate_rules ]]
       cache_policy: pull-push
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/plan.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -208,6 +223,9 @@ include:
       rules: $[[ inputs.plan_rules ]]
       cache_policy: pull
       warning_on_non_empty_plan: $[[ inputs.warning_on_non_empty_plan ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/apply.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -226,6 +244,9 @@ include:
       var_file: $[[ inputs.var_file ]]
       rules: $[[ inputs.apply_rules ]]
       cache_policy: pull
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
 
 
 # NOTE: the following configuration is only used if `trigger_in_child_pipeline` is enabled.
@@ -279,6 +300,9 @@ stages:
           plan_rules: $[[ inputs.plan_rules ]]
           apply_rules: $[[ inputs.apply_rules ]]
           warning_on_non_empty_plan: $[[ inputs.warning_on_non_empty_plan ]]
+          auto_encryption: $[[ inputs.auto_encryption ]]
+          auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+          auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
           trigger_in_child_pipeline: false
     forward:
       yaml_variables: true
diff --git a/templates/validate-plan-destroy.yml b/templates/validate-plan-destroy.yml
index 15cc048c667288abde142016a4b3c079deb17b69..d533d5d438139dab4f4699da255ef7ab642a3248 100644
--- a/templates/validate-plan-destroy.yml
+++ b/templates/validate-plan-destroy.yml
@@ -158,6 +158,18 @@ spec:
       default: [{when: on_success}]
       type: array
       description: 'Defines the `rules` of the child pipeline bridge job.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -194,6 +206,9 @@ include:
       var_file: $[[ inputs.var_file ]]
       rules: $[[ inputs.validate_rules ]]
       cache_policy: pull-push
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/plan.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -215,6 +230,9 @@ include:
       rules: $[[ inputs.plan_rules ]]
       cache_policy: pull
       warning_on_non_empty_plan: $[[ inputs.warning_on_non_empty_plan ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/destroy.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -234,6 +252,9 @@ include:
       var_file: $[[ inputs.var_file ]]
       rules: $[[ inputs.destroy_rules ]]
       cache_policy: pull
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/delete-state.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -312,6 +333,9 @@ stages:
           destroy_rules: $[[ inputs.destroy_rules ]]
           delete_state_rules: $[[ inputs.delete_state_rules ]]
           warning_on_non_empty_plan: $[[ inputs.warning_on_non_empty_plan ]]
+          auto_encryption: $[[ inputs.auto_encryption ]]
+          auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+          auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
           trigger_in_child_pipeline: false
     forward:
       yaml_variables: true
diff --git a/templates/validate-plan.yml b/templates/validate-plan.yml
index e17200705769e050f050dfb9f38e55160c45b6de..0d7d4df249fd34363c62cf2d0d19520ab9e7038a 100644
--- a/templates/validate-plan.yml
+++ b/templates/validate-plan.yml
@@ -136,6 +136,18 @@ spec:
       default: [{when: on_success}]
       type: array
       description: 'Defines the `rules` of the child pipeline bridge job.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -172,6 +184,9 @@ include:
       var_file: $[[ inputs.var_file ]]
       rules: $[[ inputs.validate_rules ]]
       cache_policy: pull-push
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
   - local: '/templates/plan.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -192,6 +207,9 @@ include:
       rules: $[[ inputs.plan_rules ]]
       cache_policy: pull
       warning_on_non_empty_plan: $[[ inputs.warning_on_non_empty_plan ]]
+      auto_encryption: $[[ inputs.auto_encryption ]]
+      auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+      auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
 
 
 # NOTE: the following configuration is only used if `trigger_in_child_pipeline` is enabled.
@@ -242,6 +260,9 @@ stages:
           validate_rules: $[[ inputs.validate_rules ]]
           plan_rules: $[[ inputs.plan_rules ]]
           warning_on_non_empty_plan: $[[ inputs.warning_on_non_empty_plan ]]
+          auto_encryption: $[[ inputs.auto_encryption ]]
+          auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
+          auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
           trigger_in_child_pipeline: false
     forward:
       yaml_variables: true
diff --git a/templates/validate.yml b/templates/validate.yml
index 072d953bd204ce7350f3bd11af0626d8dd55d553..f272bf6b3200cd3b28d93d2d026e4375d5efb57a 100644
--- a/templates/validate.yml
+++ b/templates/validate.yml
@@ -89,6 +89,18 @@ spec:
       default: pull-push
       type: string
       description: 'Defines the cache policy of the job.'
+    auto_encryption:
+      default: false
+      type: boolean
+      description: 'Whether to enable automatic state and plan encryption.'
+    auto_encryption_passphrase:
+      default: ''
+      type: string
+      description: 'Defines the passphrase to auto encrypt the state and plan. Only used if `auto_encryption` is `true`.'
+    auto_encryption_enable_migration_from_unencrypted:
+      default: false
+      type: boolean
+      description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
 
 ---
 
@@ -107,6 +119,9 @@ spec:
     GITLAB_TOFU_STATE_NAME: $[[ inputs.state_name ]]
     GITLAB_TOFU_IGNORE_INIT_ERRORS: 'true' # Tofu can report errors which might be the reason init failed.
     GITLAB_TOFU_VAR_FILE: '$[[ inputs.var_file ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
+    GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/tests/iac/main.tf b/tests/iac/main.tf
index f65a2b5a7a0681119c15026fd645f1ab8a7f8501..aa8705c6f46153325b06b6113fdab94804fa7df4 100644
--- a/tests/iac/main.tf
+++ b/tests/iac/main.tf
@@ -7,6 +7,17 @@ resource "local_file" "foo" {
   filename = "${path.module}/foo.bar"
 }
 
+locals {
+  ts = plantimestamp()
+}
+
+// NOTE: always force a change.
+resource "null_resource" "this" {
+  triggers = {
+    timestamp = local.ts
+  }
+}
+
 variable "ci_project_name" {
   type    = string
   default = "default"
@@ -24,3 +35,7 @@ output "project_name" {
 output "test_variable" {
   value = var.test_variable
 }
+
+output "this_always_changes" {
+  value = local.ts
+}
diff --git a/tests/integration-tests/AutoEncryption.gitlab-ci.yml b/tests/integration-tests/AutoEncryption.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e25b09802223f78ddda089da1ba24a76a715f07b
--- /dev/null
+++ b/tests/integration-tests/AutoEncryption.gitlab-ci.yml
@@ -0,0 +1,39 @@
+include:
+  - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/apply@$CI_COMMIT_SHA
+    inputs:
+      image_registry_base: $GITLAB_OPENTOFU_IMAGE_BASE
+      version: $CI_COMMIT_SHA
+      base_os: $GITLAB_OPENTOFU_BASE_IMAGE_OS
+      opentofu_version: $OPENTOFU_VERSION
+      stage: test
+      root_dir: $TEST_GITLAB_TOFU_ROOT_DIR
+      state_name: $TEST_GITLAB_TOFU_STATE_NAME
+      no_plan: true
+      auto_encryption: true
+      auto_encryption_passphrase: '947F23E4-B9FC-4E76-B7B4-1D35ECBE9B09'
+
+  # For CI Terraform state cleanup
+  - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/delete-state@$CI_COMMIT_SHA
+    inputs:
+      state_name: $TEST_GITLAB_TOFU_STATE_NAME
+      rules: [{when: always}]
+
+stages: [test, verify, cleanup]
+
+state-is-encrypted:
+  stage: verify
+  image: $GITLAB_OPENTOFU_IMAGE_BASE/gitlab-opentofu:$CI_COMMIT_SHA-opentofu$OPENTOFU_VERSION-alpine
+  variables:
+    GITLAB_TOFU_STATE_NAME: $TEST_GITLAB_TOFU_STATE_NAME
+  script:
+    - |
+      . $(which gitlab-tofu)
+      echo "Requesting state at $TF_HTTP_ADDRESS to check if it is encrypted ..."
+      success=$(curl --fail-with-body -u "${TF_HTTP_USERNAME}:${TF_HTTP_PASSWORD}" "${TF_HTTP_ADDRESS}" | jq -r '.encrypted_data != ""')
+      if [ "$success" != 'true' ]; then
+        echo 'Error: no encrypted data found in state.'
+        exit 1
+      else
+        echo 'Success: the state is encrypted.'
+      fi
+  rules: [{when: on_success}]
diff --git a/tests/integration-tests/AutoEncryptionMigrate.gitlab-ci.yml b/tests/integration-tests/AutoEncryptionMigrate.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c2631ad2957c9667d6da2238045f262de19418d5
--- /dev/null
+++ b/tests/integration-tests/AutoEncryptionMigrate.gitlab-ci.yml
@@ -0,0 +1,49 @@
+include:
+  - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/apply@$CI_COMMIT_SHA
+    inputs:
+      image_registry_base: $GITLAB_OPENTOFU_IMAGE_BASE
+      version: $CI_COMMIT_SHA
+      base_os: $GITLAB_OPENTOFU_BASE_IMAGE_OS
+      opentofu_version: $OPENTOFU_VERSION
+      as: 'apply:unencrypted'
+      stage: unencrypted
+      root_dir: $TEST_GITLAB_TOFU_ROOT_DIR
+      state_name: $TEST_GITLAB_TOFU_STATE_NAME
+      no_plan: true
+
+  - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/apply@$CI_COMMIT_SHA
+    inputs:
+      image_registry_base: $GITLAB_OPENTOFU_IMAGE_BASE
+      version: $CI_COMMIT_SHA
+      base_os: $GITLAB_OPENTOFU_BASE_IMAGE_OS
+      opentofu_version: $OPENTOFU_VERSION
+      as: 'apply:migrate'
+      stage: migrate
+      root_dir: $TEST_GITLAB_TOFU_ROOT_DIR
+      state_name: $TEST_GITLAB_TOFU_STATE_NAME
+      no_plan: true
+      auto_encryption: true
+      auto_encryption_passphrase: '947F23E4-B9FC-4E76-B7B4-1D35ECBE9B09'
+      auto_encryption_enable_migration_from_unencrypted: true
+
+  - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/apply@$CI_COMMIT_SHA
+    inputs:
+      image_registry_base: $GITLAB_OPENTOFU_IMAGE_BASE
+      version: $CI_COMMIT_SHA
+      base_os: $GITLAB_OPENTOFU_BASE_IMAGE_OS
+      opentofu_version: $OPENTOFU_VERSION
+      as: 'apply:encrypted'
+      stage: encrypted
+      root_dir: $TEST_GITLAB_TOFU_ROOT_DIR
+      state_name: $TEST_GITLAB_TOFU_STATE_NAME
+      no_plan: true
+      auto_encryption: true
+      auto_encryption_passphrase: '947F23E4-B9FC-4E76-B7B4-1D35ECBE9B09'
+
+  # For CI Terraform state cleanup
+  - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/delete-state@$CI_COMMIT_SHA
+    inputs:
+      state_name: $TEST_GITLAB_TOFU_STATE_NAME
+      rules: [{when: always}]
+
+stages: [unencrypted, migrate, encrypted, cleanup]
diff --git a/tests/integration-tests/WarningOnNonEmptyPlan.gitlab-ci.yml b/tests/integration-tests/WarningOnNonEmptyPlan.gitlab-ci.yml
index 1edddd7d70364a78abb67d8b4381025e72d17b83..9ec1043ea260ef05f8db0f1f2bdf7e0920424904 100644
--- a/tests/integration-tests/WarningOnNonEmptyPlan.gitlab-ci.yml
+++ b/tests/integration-tests/WarningOnNonEmptyPlan.gitlab-ci.yml
@@ -26,6 +26,7 @@ verify:plan-job:has-warning-state:
     - apk add --update curl jq
   script:
     - |
+      backend_address="${GITLAB_TOFU_STATE_ADDRESS:-${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${backend_state_name}}"
       endpoint="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs"
       is_warning_job=$(curl --silent "$endpoint" | jq -r '.[] | select(.name == "plan") | [.status == "failed", .allow_failure == true] | all')
       if [ "$is_warning_job" != 'true' ]; then
diff --git a/tests/integration.gitlab-ci.yml b/tests/integration.gitlab-ci.yml
index 8e8e0bc09db7699c0de703ee240f25833d3906cc..92e1012590b03abda8d1938f52addf228220f4c7 100644
--- a/tests/integration.gitlab-ci.yml
+++ b/tests/integration.gitlab-ci.yml
@@ -122,3 +122,21 @@ plan-job-template:
         GITLAB_OPENTOFU_BASE_IMAGE_OS:
           - alpine
           - debian
+
+apply-job-template:
+  stage: test-integration
+  variables:
+    OPENTOFU_VERSION: $LATEST_OPENTOFU_VERSION
+    TEST_GITLAB_TOFU_STATE_NAME: ci-integration-$CI_JOB_NAME_SLUG-$CI_PIPELINE_IID-$CI_NODE_INDEX
+    TEST_GITLAB_TOFU_ROOT_DIR: tests/iac
+  trigger:
+    include: tests/integration-tests/$PIPELINE_NAME.gitlab-ci.yml
+    strategy: depend
+  parallel:
+    matrix:
+      - PIPELINE_NAME:
+          - AutoEncryption
+          - AutoEncryptionMigrate
+        GITLAB_OPENTOFU_BASE_IMAGE_OS:
+          - alpine
+          - debian