diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d57e44be3d337fd915b98d44938a7c66c3a2e20b..8d8340627ffc1759c7e42e70152f7b128ff81a43 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,10 +1,12 @@
 include:
   - '/ci/build.yml'
   - '/ci/container-build.yml'
+  - '/ci/recipe-upgrade.yml'
 
 stages:
   - build-container
   - build
+  - maintenance
 
 variables:
   KAS_CONFIG: "kas/ktn-${PLATFORM}.yml:kas/series/${YOCTO_SERIES}.yml"
@@ -44,3 +46,17 @@ container:build:
 container:build-deploy:
   extends: .build-deploy-container
   <<: *container-vars
+
+# Upgrade Recipes
+.upgrade-recipe-matrix: &upgrade-recipe-matrix
+  - RECIPE: u-boot-ktn
+    LAYER: meta-ktn-imx
+    PLATFORM: mx8mm
+    YOCTO_SERIES: kirkstone
+    ASSIGNEE: fschrempf
+
+bsp:upgrade-recipes:
+  extends: .recipe-upgrade
+  parallel:
+    matrix:
+      *upgrade-recipe-matrix
diff --git a/ci/recipe-upgrade.yml b/ci/recipe-upgrade.yml
new file mode 100644
index 0000000000000000000000000000000000000000..754649ac1e77a4afe814760320fa76c202d00a63
--- /dev/null
+++ b/ci/recipe-upgrade.yml
@@ -0,0 +1,60 @@
+#
+# This is a template for the recipe upgrade job that uses devtool to upgrade a
+# recipe to the latest version available in the upstream repo and create a MR
+# for this change.
+#
+.recipe-upgrade:
+  tags:
+    - server
+  stage: maintenance
+  image: $BUILD_IMAGE
+  rules:
+    - if: '($CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web")'
+      when: always
+    - when: never
+  variables:
+    RECIPE: ""
+    LAYER: ""
+    FEAT_BRANCH: feature/${CI_COMMIT_REF_NAME}/upgrade-$RECIPE
+    REVIEWER: ""
+    ASSIGNEE: ""
+  before_script:
+    - git config --global user.email "upgrade-bot@${CI_SERVER_HOST}"
+    - git config --global user.name "Upgrade Bot"
+  script:
+    - git branch -D ${FEAT_BRANCH} || true
+    - git checkout -b ${FEAT_BRANCH}
+    - |
+      kas shell -c " \
+        devtool upgrade --no-patch --no-overrides $RECIPE && \
+        devtool finish --mode srcrev --no-overrides --remove-work $RECIPE $LAYER" \
+        $KAS_CONFIG:kas/dev/shared-cache.yml
+    - git add .
+    - 'git commit -m "$LAYER: $RECIPE: Upgrade to latest revision"'
+    - git push --force https://upgrade-bot:${PROJECT_ACCESS_TOKEN}@${CI_REPOSITORY_URL#*@}
+    - | # get the id of the assignee username
+      if [ -n "$ASSIGNEE" ]; then
+        ASSIGNEE_ID=$(gitlab -o json --server-url ${CI_SERVER_URL} \
+              --private-token ${PROJECT_ACCESS_TOKEN} \
+              user list \
+              --username $ASSIGNEE | jq .[0].id)
+        MR_PARAMS="--assignee-id $ASSIGNEE_ID"
+      fi
+    - | # get the id of the reviewer username
+      if [ -n "$REVIEWER" ]; then
+        REVIEWER_ID=$(gitlab -o json --server-url ${CI_SERVER_URL} \
+              --private-token ${PROJECT_ACCESS_TOKEN} \
+              user list \
+              --username $REVIEWER | jq .[0].id)
+        MR_PARAMS="$MR_PARAMS --reviewer-id $REVIEWER_ID"
+      fi
+    - |
+      gitlab --server-url ${CI_SERVER_URL} \
+             --private-token ${PROJECT_ACCESS_TOKEN} \
+             project-merge-request create \
+             --project-id ${CI_PROJECT_PATH} \
+             --source-branch ${FEAT_BRANCH} \
+             --target-branch ${CI_COMMIT_REF_NAME} \
+             --remove-source-branch true \
+             $MR_PARAMS \
+             --title "🤖 Upgrade Bot: Upgrade ${RECIPE} to latest revision"