diff --git a/doc/source/general-jobs.rst b/doc/source/general-jobs.rst
index 56d6e4c50..5d75b50cf 100644
--- a/doc/source/general-jobs.rst
+++ b/doc/source/general-jobs.rst
@@ -7,5 +7,6 @@ General Purpose Jobs
 .. zuul:autojob:: markdownlint
 .. zuul:autojob:: multinode
 .. zuul:autojob:: run-test-command
+.. zuul:autojob:: shake-build
 .. zuul:autojob:: upload-git-mirror
 .. zuul:autojob:: validate-zone-db
diff --git a/doc/source/general-roles.rst b/doc/source/general-roles.rst
index 248e3531b..b856c517d 100644
--- a/doc/source/general-roles.rst
+++ b/doc/source/general-roles.rst
@@ -18,6 +18,7 @@ General Purpose Roles
 .. zuul:autorole:: ensure-dhall
 .. zuul:autorole:: ensure-dstat-graph
 .. zuul:autorole:: ensure-markdownlint
+.. zuul:autorole:: ensure-shake
 .. zuul:autorole:: fetch-markdownlint
 .. zuul:autorole:: git-prepare-nodecache
 .. zuul:autorole:: log-inventory
@@ -38,6 +39,7 @@ General Purpose Roles
 .. zuul:autorole:: remove-zuul-sshkey
 .. zuul:autorole:: revoke-sudo
 .. zuul:autorole:: run-dstat
+.. zuul:autorole:: shake-build
 .. zuul:autorole:: sign-artifacts
 .. zuul:autorole:: stage-output
 .. zuul:autorole:: start-zuul-console
diff --git a/playbooks/shake/pre.yaml b/playbooks/shake/pre.yaml
new file mode 100644
index 000000000..122175c1a
--- /dev/null
+++ b/playbooks/shake/pre.yaml
@@ -0,0 +1,3 @@
+- hosts: all
+  roles:
+    - ensure-shake
diff --git a/playbooks/shake/run.yaml b/playbooks/shake/run.yaml
new file mode 100644
index 000000000..b0473c1eb
--- /dev/null
+++ b/playbooks/shake/run.yaml
@@ -0,0 +1,3 @@
+- hosts: all
+  roles:
+    - shake-build
diff --git a/roles/ensure-shake/README.rst b/roles/ensure-shake/README.rst
new file mode 100644
index 000000000..d3e62f9f6
--- /dev/null
+++ b/roles/ensure-shake/README.rst
@@ -0,0 +1,3 @@
+Ensure shake is installed
+
+Installs the shake tool using the distro package.
diff --git a/roles/ensure-shake/tasks/main.yaml b/roles/ensure-shake/tasks/main.yaml
new file mode 100644
index 000000000..d7c0ffd00
--- /dev/null
+++ b/roles/ensure-shake/tasks/main.yaml
@@ -0,0 +1,31 @@
+- name: Make sure the role is run on supported distro
+  fail:
+    msg: |
+      This role supports Fedora only,
+      Add your distro package to: {{ opendev_url }}/roles/ensure-shake/vars/
+  when: "ansible_distribution != 'Fedora'"
+  vars:
+    opendev_url: https://opendev.org/zuul/zuul-jobs/src/branch/master/
+
+- name: Check shake version
+  command: shake --version
+  failed_when: false
+  register: _shake_version
+
+- block:
+    - name: Include OS-specific variables
+      include_vars: "{{ zj_distro_os }}"
+      loop_control:
+        loop_var: zj_distro_os
+      with_first_found:
+        - "{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yaml"
+        - "{{ ansible_distribution }}.{{ ansible_architecture }}.yaml"
+        - "{{ ansible_distribution }}.yaml"
+        - "{{ ansible_os_family }}.yaml"
+
+    - name: Install shake
+      package:
+        name: "{{ shake_packages }}"
+        state: present
+      become: yes
+  when: "_shake_version.rc != 0"
diff --git a/roles/ensure-shake/vars/RedHat.yaml b/roles/ensure-shake/vars/RedHat.yaml
new file mode 100644
index 000000000..0bd31959a
--- /dev/null
+++ b/roles/ensure-shake/vars/RedHat.yaml
@@ -0,0 +1,3 @@
+shake_packages:
+  - shake
+  - ghc-shake-devel
diff --git a/roles/shake-build/README.rst b/roles/shake-build/README.rst
new file mode 100644
index 000000000..8e5714b71
--- /dev/null
+++ b/roles/shake-build/README.rst
@@ -0,0 +1,13 @@
+Run the shake build system command.
+
+**Role Variables**
+
+.. zuul:rolevar:: shake_report_name
+   :default: shake.html
+
+   The name of the report.
+
+.. zuul:rolevar:: zuul_work_dir
+   :default: {{ zuul.project.src_dir }}
+
+   Directory to run the shake command in.
diff --git a/roles/shake-build/defaults/main.yaml b/roles/shake-build/defaults/main.yaml
new file mode 100644
index 000000000..052c284d1
--- /dev/null
+++ b/roles/shake-build/defaults/main.yaml
@@ -0,0 +1,3 @@
+shake_report_name: "shake.html"
+
+zuul_work_dir: "{{ zuul.project.src_dir }}"
diff --git a/roles/shake-build/tasks/main.yaml b/roles/shake-build/tasks/main.yaml
new file mode 100644
index 000000000..c5a953c14
--- /dev/null
+++ b/roles/shake-build/tasks/main.yaml
@@ -0,0 +1,16 @@
+- block:
+    - name: Run shake
+      command: "shake --report={{ report_location }}"
+      vars:
+        report_location: "{{ ansible_user_dir }}/zuul-output/logs/{{ shake_report_name }}"
+      args:
+        chdir: "{{ zuul_work_dir }}"
+
+  always:
+    - name: Return report to Zuul
+      zuul_return:
+        data:
+          zuul:
+            artifacts:
+              - name: "Shake report"
+                url: "{{ shake_report_name }}"
diff --git a/test-playbooks/shake/setup-project.yaml b/test-playbooks/shake/setup-project.yaml
new file mode 100644
index 000000000..b7d06c5be
--- /dev/null
+++ b/test-playbooks/shake/setup-project.yaml
@@ -0,0 +1,19 @@
+- hosts: all
+  tasks:
+    - name: Setup shakefile
+      copy:
+        dest: "{{ zuul.project.src_dir }}/Shakefile.hs"
+        content: |
+          import Development.Shake
+
+          -- This Shakefile creates synthetic load using sha256sum on each .rst file
+          main :: IO ()
+          main = shakeArgs shakeOptions{shakeFiles="_build"} $ do
+            want [ "doc" ]
+
+            phony "doc" $ do
+              files <- getDirectoryFiles "" ["//*.rst"]
+              putNormal $ "Processing doc... with: " <> show files
+              need files
+
+            "//**.rst" %> \rst_file -> cmd_ "sha256sum " [rst_file]
diff --git a/zuul-tests.d/general-roles-jobs.yaml b/zuul-tests.d/general-roles-jobs.yaml
index 0311ca28a..c1b15a0df 100644
--- a/zuul-tests.d/general-roles-jobs.yaml
+++ b/zuul-tests.d/general-roles-jobs.yaml
@@ -692,6 +692,22 @@
       - ^roles/upload-git-mirror/.*
       - ^test-playbooks/upload-git-mirror.yaml
 
+- job:
+    name: zuul-jobs-test-shake-build
+    description: Test the shake build job and roles
+    parent: shake-build
+    files:
+      - playbooks/shake/.*
+      - roles/ensure-shake/.*
+      - roles/shake-build/.*
+      - test-playbooks/shake/.*
+    pre-run:
+      - test-playbooks/shake/setup-project.yaml
+    nodeset:
+      nodes:
+        - name: fedora-31
+          label: fedora-31
+
 # -* AUTOGENERATED *-
 #  The following project section is autogenerated by
 #    tox -e update-test-platforms
@@ -746,6 +762,7 @@
         - zuul-jobs-test-generate-zuul-manifest
         - zuul-jobs-test-upload-artifactory
         - zuul-jobs-test-upload-git-mirror
+        - zuul-jobs-test-shake-build
     gate:
       jobs:
         - zuul-jobs-test-add-authorized-keys
@@ -790,3 +807,4 @@
         - zuul-jobs-test-generate-zuul-manifest
         - zuul-jobs-test-upload-artifactory
         - zuul-jobs-test-upload-git-mirror
+        - zuul-jobs-test-shake-build
diff --git a/zuul.d/general-jobs.yaml b/zuul.d/general-jobs.yaml
index 1e3c69d07..f0a4a20af 100644
--- a/zuul.d/general-jobs.yaml
+++ b/zuul.d/general-jobs.yaml
@@ -119,3 +119,12 @@
 
     pre-run: playbooks/dhall/prepare.yaml
     run: playbooks/dhall/diff.yaml
+
+- job:
+    name: shake-build
+    description: |
+      Run the shake build system command.
+
+      This job produces a shake.html report.
+    pre-run: playbooks/shake/pre.yaml
+    run: playbooks/shake/run.yaml