diff --git a/jobs/tools/jenkins-jobs-builder.groovy.j2 b/jobs/tools/jenkins-jobs-builder.groovy.j2
new file mode 100644
index 0000000..5cf4bd6
--- /dev/null
+++ b/jobs/tools/jenkins-jobs-builder.groovy.j2
@@ -0,0 +1,56 @@
+pipeline {
+  agent any
+
+  stages {
+
+    stage('Checkout Repository') {
+      steps {
+        updateGitlabCommitStatus name: 'jenkins', state: 'running'
+        checkout([
+          $class: 'GitSCM',
+          branches: [[name: env.gitlabSourceBranch ?: "master"]],
+          userRemoteConfigs: [[
+            url: '{{gitlab_url}}/{{gitlab_project_name}}.git',
+            refspec: '+refs/heads/*:refs/remotes/origin/* \
+                      +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*'
+          ]],
+        ])
+      }
+    }
+
+    stage('Test Jenkins jobs can be generated') {
+      steps {
+        sh('tox')
+      }
+    }
+
+    stage('Update Jenkins jobs and setup gitlab webhooks') {
+      when {
+        // really update jenkins jobs configuration when pushing commits to master
+        // or when manually triggering the job from Jenkins UI
+        expression { !env.gitlabMergeRequestIid }
+        anyOf {
+          expression { !env.gitlabSourceBranch }
+          expression { "${env.gitlabSourceBranch}" == "master" }
+        }
+      }
+      steps {
+        script {
+          sh('tox -- update --delete-old')
+        }
+      }
+    }
+  }
+
+  post {
+    failure {
+      updateGitlabCommitStatus name: 'jenkins', state: 'failed'
+    }
+    success {
+      updateGitlabCommitStatus name: 'jenkins', state: 'success'
+    }
+    aborted {
+      updateGitlabCommitStatus name: 'jenkins', state: 'canceled'
+    }
+  }
+}
diff --git a/jobs/tools/jenkins-jobs-builder.yaml b/jobs/tools/jenkins-jobs-builder.yaml
new file mode 100644
index 0000000..cf9e48d
--- /dev/null
+++ b/jobs/tools/jenkins-jobs-builder.yaml
@@ -0,0 +1,31 @@
+- job-template:
+    name: jenkins-tools/{project_name}
+    project-type: pipeline
+    description: Update jenkins jobs and setup GitLab webhooks
+    node: built-in
+    gitlab_url: https://gitlab-staging.swh.network
+    gitlab_connection_name: gitlab-staging
+    gitlab_project_name: swh/infra/ci-cd/swh-jenkins-jobs
+    properties:
+      - build-discarder:
+          days-to-keep: 7
+    properties:
+      - gitlab:
+          connection: "{gitlab_connection_name}"
+    triggers:
+      - gitlab:
+          trigger-push: true
+          trigger-merge-request: true
+          add-ci-message: true
+          cancel-pending-builds-on-update: true
+          # secret jenkins token is generated when executing tox
+          secret-token: !include-raw: jobs/templates/jenkins-token
+
+    dsl: !include-jinja2: jenkins-jobs-builder.groovy.j2
+
+# we use a project and a job template here as we need jinja2 processing
+- project:
+    name: swh-jenkins-jobs-builder
+    project_name: swh-jenkins-jobs-builder
+    jobs:
+      - "jenkins-tools/{project_name}"