diff --git a/src/gitlab-tofu.sh b/src/gitlab-tofu.sh
index 116af65cd265b717f73ff7e06ba6cfdd91e127f1..494a5d7ba7bf0efd171db91b6544f598577ff98a 100644
--- a/src/gitlab-tofu.sh
+++ b/src/gitlab-tofu.sh
@@ -214,6 +214,10 @@ if [ $sourced -eq 0 ]; then
       $TF_IMPLICIT_INIT && terraform_init -backend=false
       tofu "${TF_CHDIR_OPT}" "${@}"
     ;;
+    "graph")
+      $TF_IMPLICIT_INIT && terraform_init
+      tofu "${TF_CHDIR_OPT}" "${@}"
+    ;;
     --)
       shift
       tofu "${TF_CHDIR_OPT}" "${@}"
diff --git a/templates/graph.yml b/templates/graph.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b314c0740d6f16e5548165cd5fc1344f3277d412
--- /dev/null
+++ b/templates/graph.yml
@@ -0,0 +1,79 @@
+spec:
+  inputs:
+    # Job and Stage name
+    as:
+      default: 'graph'
+      description: 'Defines the name of this job.'
+    stage:
+      default: 'validate'
+      description: 'Defines the stage that this job will belong to.'
+
+    # 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.'
+    graph_file:
+      default: 'graph.dot'
+      description: |
+        Name of the graph file that should be generated.
+        It will be uploaded as an artifact.
+
+---
+
+'$[[ inputs.as ]]':
+  stage: $[[ inputs.stage ]]
+  needs: []
+  cache:
+    key: "$__CACHE_KEY_HACK"
+    paths:
+      - $TF_ROOT/.terraform/
+  variables:
+    # FIXME: work around to make slashes work in `cache:key`. see https://gitlab.com/gitlab-org/gitlab/-/issues/439898
+    __CACHE_KEY_HACK: "$[[ inputs.root_dir ]]"
+    TF_ROOT: $[[ inputs.root_dir ]]
+    TF_STATE_NAME: $[[ inputs.state_name ]]
+  image:
+    name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]'
+  script:
+    - gitlab-tofu graph > "$[[ inputs.graph_file ]]"
+  artifacts:
+    paths:
+      - "$[[ inputs.graph_file ]]"
diff --git a/templates/job-templates.yml b/templates/job-templates.yml
index 31338dacc39255c4446a1561615be0ded551f101..a7db0f3d743c5ab4b8eb4e66aa30975f38bd3658 100644
--- a/templates/job-templates.yml
+++ b/templates/job-templates.yml
@@ -93,6 +93,15 @@ include:
       image_name: $[[ inputs.image_name ]]
       root_dir: $[[ inputs.root_dir ]]
       state_name: $[[ inputs.state_name ]]
+  - local: '/templates/graph.yml'
+    inputs:
+      as: '$[[ inputs.job_name_prefix ]]graph'
+      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/test.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]test'
diff --git a/tests/integration-tests/JobTemplates.gitlab-ci.yml b/tests/integration-tests/JobTemplates.gitlab-ci.yml
index b85fccd0555e23867b78c5902b8b69892f762b0d..6c0acc9700cfa6cd1af2e5f49b0506d2560232f4 100644
--- a/tests/integration-tests/JobTemplates.gitlab-ci.yml
+++ b/tests/integration-tests/JobTemplates.gitlab-ci.yml
@@ -19,6 +19,10 @@ validate:
   extends: '.opentofu:validate'
   rules: [{when: always}]
 
+graph:
+  extends: '.opentofu:graph'
+  rules: [{when: always}]
+
 plan:
   extends: '.opentofu:plan'
   rules: [{when: always}]