diff --git a/.gitlab/README.md.template b/.gitlab/README.md.template
index 520248f81b5cdbfe8ab703deec2ff1331f0c642b..2120dba08024c808a71cb3853c909daa041965e8 100644
--- a/.gitlab/README.md.template
+++ b/.gitlab/README.md.template
@@ -105,6 +105,7 @@ but no destructive actions.
 
 - [`validate-plan`](templates/validate-plan.yml)
 - [`validate-plan-apply`](templates/validate-plan-apply.yml)
+- [`validate-plan-destroy`](templates/validate-plan-destroy.yml)
 
 ### Job Templates
 
diff --git a/README.md b/README.md
index fb79a6165ef33ce7b3d9738bcf0d216834ee9632..44a22673162cd066e52450e75c70f1a0d3d68de5 100644
--- a/README.md
+++ b/README.md
@@ -107,6 +107,7 @@ but no destructive actions.
 
 - [`validate-plan`](templates/validate-plan.yml)
 - [`validate-plan-apply`](templates/validate-plan-apply.yml)
+- [`validate-plan-destroy`](templates/validate-plan-destroy.yml)
 
 ### Job Templates
 
diff --git a/src/gitlab-tofu.sh b/src/gitlab-tofu.sh
index d168c5814b4defaa1791f53bc275c465bb29a203..0f8b02b5949b2822524cd6149de483a51ccb2d5a 100644
--- a/src/gitlab-tofu.sh
+++ b/src/gitlab-tofu.sh
@@ -16,6 +16,10 @@ if [ -z "$TF_FF_AUTO_URLENCODE_STATE_NAME" ]; then
   TF_FF_AUTO_URLENCODE_STATE_NAME=true
 fi
 
+if [ -z "$TF_FF_AUTO_APPROVE_APPLY" ]; then
+  TF_FF_AUTO_APPROVE_APPLY=true
+fi
+
 # Helpers
 
 # Evaluate if this script is being sourced or executed directly.
@@ -62,25 +66,33 @@ if [ -n "${TF_STATE_NAME}" ] && [ -z "${TF_ADDRESS}" ]; then
   TF_ADDRESS="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}"
 fi
 
+if [ -z "${TF_PLAN_NAME}" ]; then
+  TF_PLAN_NAME=plan
+fi
+
+if [ -z "${TF_APPLY_NO_PLAN}" ]; then
+  TF_APPLY_NO_PLAN=false
+fi
+
 # If TF_ROOT is set then use the -chdir option
 if [ -n "${TF_ROOT}" ]; then
   abs_tf_root=$(cd "${CI_PROJECT_DIR}"; realpath "${TF_ROOT}")
 
   TF_CHDIR_OPT="-chdir=${abs_tf_root}"
 
-  default_tf_plan_cache="${abs_tf_root}/plan.cache"
-  default_tf_plan_json="${abs_tf_root}/plan.json"
+  default_tf_plan_cache="${abs_tf_root}/${TF_PLAN_NAME}.cache"
+  default_tf_plan_json="${abs_tf_root}/${TF_PLAN_NAME}.json"
 fi
 
 
 # If TF_PLAN_CACHE is not set then use either the plan.cache file within TF_ROOT if set, or plan.cache in CWD
 if [ -z "${TF_PLAN_CACHE}" ]; then
-  TF_PLAN_CACHE="${default_tf_plan_cache:-plan.cache}"
+  TF_PLAN_CACHE="${default_tf_plan_cache:-${TF_PLAN_NAME}.cache}"
 fi
 
 # If TF_PLAN_JSON is not set then use either the plan.json file within TF_ROOT if set, or plan.json in CWD
 if [ -z "${TF_PLAN_JSON}" ]; then
-  TF_PLAN_JSON="${default_tf_plan_json:-plan.json}"
+  TF_PLAN_JSON="${default_tf_plan_json:-${TF_PLAN_NAME}.json}"
 fi
 
 # Set variables for the HTTP backend to default to TF_* values
@@ -157,8 +169,17 @@ if [ $sourced -eq 0 ]; then
 
   case "${1}" in
     "apply")
+      auto_approve_args=""
+      if [ "${TF_FF_AUTO_APPROVE_APPLY}" = true ]; then
+        auto_approve_args="-auto-approve"
+      fi
+
       $TF_IMPLICIT_INIT && terraform_init
-      tofu "${TF_CHDIR_OPT}" "${@}" -input=false "${TF_PLAN_CACHE}"
+      if [ "$TF_APPLY_NO_PLAN" = false ]; then
+        tofu "${TF_CHDIR_OPT}" "${@}" -input=false "${auto_approve_args}" "${TF_PLAN_CACHE}"
+      else
+        tofu "${TF_CHDIR_OPT}" "${@}" -input=false "${auto_approve_args}"
+      fi
     ;;
     "destroy")
       $TF_IMPLICIT_INIT && terraform_init
diff --git a/templates/apply.yml b/templates/apply.yml
index b4c321f7bea6712d33c937e02a8771ee3456c358..e2d37785e6a28c5aa3c51d7e1e64c37ed004d1b1 100644
--- a/templates/apply.yml
+++ b/templates/apply.yml
@@ -50,6 +50,13 @@ spec:
     state_name:
       default: default
       description: 'Remote OpenTofu state name.'
+    no_plan:
+      default: false
+      type: boolean
+      description: 'Whether a plan file should be used.'
+    plan_name:
+      default: 'plan'
+      description: 'The name of the plan file to use. Will be used for TF_PLAN_CACHE and TF_PLAN_JSON.'
     auto_apply:
       default: false
       type: boolean
@@ -76,6 +83,8 @@ spec:
     __CACHE_KEY_HACK: "$[[ inputs.root_dir ]]"
     TF_ROOT: $[[ inputs.root_dir ]]
     TF_STATE_NAME: $[[ inputs.state_name ]]
+    TF_APPLY_NO_PLAN: $[[ inputs.no_plan ]]
+    TF_PLAN_NAME: $[[ inputs.plan_name ]]
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]'
   script:
diff --git a/templates/destroy.yml b/templates/destroy.yml
index 97cb73657121471871e355e5b9828c7c87de67dd..cd9a743184f27352aac9d355f297f24645db7c47 100644
--- a/templates/destroy.yml
+++ b/templates/destroy.yml
@@ -50,6 +50,13 @@ spec:
     state_name:
       default: default
       description: 'Remote OpenTofu state name.'
+    no_plan:
+      default: true
+      type: boolean
+      description: 'Whether a plan file should be used.'
+    plan_name:
+      default: 'destroy-plan'
+      description: 'The name of the plan file to use. Will be used for TF_PLAN_CACHE and TF_PLAN_JSON.'
     auto_destroy:
       default: false
       type: boolean
@@ -75,7 +82,9 @@ spec:
     __CACHE_KEY_HACK: "$[[ inputs.root_dir ]]"
     TF_ROOT: $[[ inputs.root_dir ]]
     TF_STATE_NAME: $[[ inputs.state_name ]]
+    TF_APPLY_NO_PLAN: $[[ inputs.no_plan ]]
+    TF_PLAN_NAME: $[[ inputs.plan_name ]]
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]'
   script:
-    - gitlab-tofu destroy
+    - gitlab-tofu apply -destroy
diff --git a/templates/plan.yml b/templates/plan.yml
index 7441ebddd57b6037a1ecdf0860119b0a610d3e98..a28b9c614cf3439d695691141beacf8a40ddcfbf 100644
--- a/templates/plan.yml
+++ b/templates/plan.yml
@@ -50,6 +50,13 @@ spec:
     state_name:
       default: default
       description: 'Remote OpenTofu state name.'
+    plan_name:
+      default: 'plan'
+      description: 'The name of the plan cache and plan json file.'
+    destroy:
+      default: false
+      type: boolean
+      description: 'Indicate if the plan should be a destroy plan. You may want to change the `plan_name` input to `destroy-plan` which is the default for the destroy job.'
 
 ---
 
@@ -68,9 +75,9 @@ spec:
     # See: https://docs.gitlab.com/ee/ci/yaml/#artifactspublic
     public: false
     paths:
-      - $TF_ROOT/plan.cache
+      - $TF_ROOT/$[[ inputs.plan_name ]].cache
     reports:
-      terraform: $TF_ROOT/plan.json
+      terraform: $TF_ROOT/$[[ inputs.plan_name]].json
   rules:
     - if: $CI_PIPELINE_SOURCE == "merge_request_event"
     - if: $CI_OPEN_MERGE_REQUESTS  # Don't add it to a *branch* pipeline if it's already in a merge request pipeline.
@@ -85,8 +92,15 @@ spec:
     __CACHE_KEY_HACK: "$[[ inputs.root_dir ]]"
     TF_ROOT: $[[ inputs.root_dir ]]
     TF_STATE_NAME: $[[ inputs.state_name ]]
+    TF_PLAN_NAME: $[[ inputs.plan_name ]]
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]'
   script:
-    - gitlab-tofu plan
+    - |
+      args=""
+      if [ "$[[ inputs.destroy ]]" == "true" ]; then 
+        echo "Planning for a destroy"
+        args="-destroy"
+      fi
+    - gitlab-tofu plan $args
     - gitlab-tofu plan-json
diff --git a/templates/validate-plan-destroy.yml b/templates/validate-plan-destroy.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b94fc546fab0e98a9456fa4df5b7b354669a86ac
--- /dev/null
+++ b/templates/validate-plan-destroy.yml
@@ -0,0 +1,119 @@
+spec:
+  inputs:
+    # Stages
+    stage_validate:
+      default: 'validate'
+      description: 'Defines the validate stage. This stage includes the `fmt` and `validate` jobs.'
+    stage_build:
+      default: 'build'
+      description: 'Defines the build stage. This stage includes the `plan` job.'
+    stage_cleanup:
+      default: 'cleanup'
+      description: 'Defines the cleanup stage. This stage includes the `destroy` and `delete-state` jobs.'
+
+    # Versions
+    # This version is only required, because we cannot access the context of the component,
+    # see https://gitlab.com/gitlab-org/gitlab/-/issues/438275
+    version:
+      default: 'latest'
+      description: 'Version of this component. Has to be the same as the one in the component include entry.'
+
+    opentofu_version:
+      default: '1.7.1'
+      options:
+        - '$OPENTOFU_VERSION'
+        - '1.7.1'
+        - '1.7.0'
+        - '1.7.0-alpha1'
+        - '1.6.2'
+        - '1.6.1'
+        - '1.6.0'
+      description: 'OpenTofu version that should be used.'
+
+    # Images
+    image_registry_base:
+      default: '$CI_REGISTRY/components/opentofu'
+      description: 'Host URI to the job images. Will be combined with `image_name` to construct the actual image URI.'
+    # FIXME: not yet possible because of https://gitlab.com/gitlab-org/gitlab/-/issues/438722
+    # gitlab_opentofu_image:
+    #   # FIXME: This should reference the component tag that is used.
+    #   #        Currently, blocked by https://gitlab.com/gitlab-org/gitlab/-/issues/438275
+    #   # default: '$CI_REGISTRY/components/opentofu/gitlab-opentofu:$[[ inputs.opentofu_version ]]'
+    #   default: '$CI_REGISTRY/components/opentofu/gitlab-opentofu:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]'
+    #   description: 'Tag of the gitlab-opentofu image.'
+
+    image_name:
+      default: 'gitlab-opentofu'
+      description: 'Image name for the job images. Hosted under `image_registry_base`.'
+
+    # Configuration
+    root_dir:
+      default: ${CI_PROJECT_DIR}
+      description: 'Root directory for the OpenTofu project.'
+    state_name:
+      default: default
+      description: 'Remote OpenTofu state name.'
+    plan_name:
+      default: 'destroy-plan'
+      description: 'Destroy plan file name.'
+    auto_destroy:
+      default: false
+      type: boolean
+      description: 'Whether the destroy job is manual or automatically run.'
+
+---
+
+include:
+  - local: '/templates/fmt.yml'
+    inputs:
+      as: 'fmt'
+      stage: $[[ inputs.stage_validate ]]
+      version: $[[ inputs.version ]]
+      opentofu_version: $[[ inputs.opentofu_version ]]
+      image_registry_base: $[[ inputs.image_registry_base ]]
+      image_name: $[[ inputs.image_name ]]
+      root_dir: $[[ inputs.root_dir ]]
+  - local: '/templates/validate.yml'
+    inputs:
+      as: 'validate'
+      stage: $[[ inputs.stage_validate ]]
+      version: $[[ inputs.version ]]
+      opentofu_version: $[[ inputs.opentofu_version ]]
+      image_registry_base: $[[ inputs.image_registry_base ]]
+      image_name: $[[ inputs.image_name ]]
+      root_dir: $[[ inputs.root_dir ]]
+      state_name: $[[ inputs.state_name ]]
+  - local: '/templates/plan.yml'
+    inputs:
+      as: 'plan'
+      stage: $[[ inputs.stage_build ]]
+      version: $[[ inputs.version ]]
+      opentofu_version: $[[ inputs.opentofu_version ]]
+      image_registry_base: $[[ inputs.image_registry_base ]]
+      image_name: $[[ inputs.image_name ]]
+      root_dir: $[[ inputs.root_dir ]]
+      state_name: $[[ inputs.state_name ]]
+      plan_name: $[[ inputs.plan_name ]]
+      destroy: true
+  - local: '/templates/destroy.yml'
+    inputs:
+      as: 'destroy'
+      stage: $[[ inputs.stage_cleanup ]]
+      version: $[[ inputs.version ]]
+      opentofu_version: $[[ inputs.opentofu_version ]]
+      image_registry_base: $[[ inputs.image_registry_base ]]
+      image_name: $[[ inputs.image_name ]]
+      root_dir: $[[ inputs.root_dir ]]
+      state_name: $[[ inputs.state_name ]]
+      no_plan: false
+      plan_name: $[[ inputs.plan_name ]]
+      auto_destroy: $[[ inputs.auto_destroy ]]
+  - local: '/templates/delete-state.yml'
+    inputs:
+      as: 'delete-state'
+      stage: $[[ inputs.stage_cleanup ]]
+      state_name: $[[ inputs.state_name ]]
+
+# NOTE: we have to define this `needs` here, because inputs don't support arrays, yet.
+delete-state:
+  needs: [destroy]
diff --git a/tests/integration-tests/Destroy.gitlab-ci.yml b/tests/integration-tests/Destroy.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d0ec3c56ae349723dedf34cdfd13d4561621ec08
--- /dev/null
+++ b/tests/integration-tests/Destroy.gitlab-ci.yml
@@ -0,0 +1,43 @@
+include:
+  - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/apply@$CI_COMMIT_SHA
+    inputs:
+      image_registry_base: $GITLAB_OPENTOFU_IMAGE_BASE
+      version: $CI_COMMIT_SHA
+      opentofu_version: $OPENTOFU_VERSION
+      as: 'setup:apply'
+      stage: setup
+      root_dir: $TEST_TF_ROOT
+      state_name: $TEST_TF_STATE_NAME
+      no_plan: true
+      auto_apply: true
+
+  - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/validate-plan-destroy@$CI_COMMIT_SHA
+    inputs:
+      image_registry_base: $GITLAB_OPENTOFU_IMAGE_BASE
+      version: $CI_COMMIT_SHA
+      opentofu_version: $OPENTOFU_VERSION
+      root_dir: $TEST_TF_ROOT
+      state_name: $TEST_TF_STATE_NAME
+
+stages: [setup, validate, build, cleanup]
+
+# Required to run everything immediately, instead of manually.
+
+'setup:apply':
+  rules: [{when: always}]
+
+fmt:
+  rules: [{when: always}]
+
+validate:
+  rules: [{when: always}]
+
+plan:
+  rules: [{when: always}]
+
+destroy:
+  rules: [{when: always}]
+
+delete-state:
+  rules: [{when: always}]
+
diff --git a/tests/integration.gitlab-ci.yml b/tests/integration.gitlab-ci.yml
index 8f490d7441e320cd8ed19329c3e3e86bc231a7c4..ca0369b60c6531be1b47a082ae0b61b0b39213cf 100644
--- a/tests/integration.gitlab-ci.yml
+++ b/tests/integration.gitlab-ci.yml
@@ -12,3 +12,4 @@ component:
       - PIPELINE_NAME: [Defaults]
       - PIPELINE_NAME: [JobTemplates]
       - PIPELINE_NAME: [TestJob]
+      - PIPELINE_NAME: [Destroy]