diff --git a/.github/filters.yml b/.github/filters.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bde1a69df6a34f7bf253ac3a73978cc61fde229b
--- /dev/null
+++ b/.github/filters.yml
@@ -0,0 +1,180 @@
+{
+  "ath79-generic": [
+    "targets/ath79-generic",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "ath79-nand": [
+    "targets/ath79-nand",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "bcm27xx-bcm2708": [
+    "targets/bcm27xx-bcm2708",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "bcm27xx-bcm2709": [
+    "targets/bcm27xx-bcm2709",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "ipq40xx-generic": [
+    "targets/ipq40xx-generic",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "ipq806x-generic": [
+    "targets/ipq806x-generic",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "lantiq-xrx200": [
+    "targets/lantiq-xrx200",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "lantiq-xway": [
+    "targets/lantiq-xway",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "mediatek-mt7622": [
+    "targets/mediatek-mt7622",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "mpc85xx-p1010": [
+    "targets/mpc85xx-p1010",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "mpc85xx-p1020": [
+    "targets/mpc85xx-p1020",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "ramips-mt7620": [
+    "targets/ramips-mt7620",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "ramips-mt7621": [
+    "targets/ramips-mt7621",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "ramips-mt76x8": [
+    "targets/ramips-mt76x8",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "rockchip-armv8": [
+    "targets/rockchip-armv8",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "sunxi-cortexa7": [
+    "targets/sunxi-cortexa7",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "x86-generic": [
+    "targets/x86-generic",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "x86-geode": [
+    "targets/x86-geode",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "x86-legacy": [
+    "targets/x86-legacy",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "x86-64": [
+    "targets/x86-64",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk",
+    "contrib/ci/minimal-site/**",
+    "package/**"
+  ],
+  "bcm27xx-bcm2710": [
+    "targets/bcm27xx-bcm2710",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ],
+  "mvebu-cortexa9": [
+    "targets/mvebu-cortexa9",
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk"
+  ]
+}
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index 2312b47b9c6dac92652d03a03b2ca243daaf49a3..ccd51090c781dce59fd8a67304dc4336e0c8cfbc 100644
--- a/.github/workflows/build-docs.yml
+++ b/.github/workflows/build-docs.yml
@@ -14,7 +14,7 @@ jobs:
     name: docs
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
       - name: Install Dependencies
         run: sudo pip3 install sphinx-rtd-theme
       - name: Build documentation
diff --git a/.github/workflows/build-gluon.yml b/.github/workflows/build-gluon.yml
index 944870aa17120a324504fe503af2ab9e9d76cf28..897e7744f917361aec7d2a87b6ab0bbdbcd45533 100644
--- a/.github/workflows/build-gluon.yml
+++ b/.github/workflows/build-gluon.yml
@@ -9,43 +9,47 @@ on:
       - master
       - next*
       - v20*
-    paths:
-      - "modules"
-      - "Makefile"
-      - "scripts/**"
-      - "package/**"
-      - "patches/**"
-      - "targets/**"
-      - ".github/workflows/build-gluon.yml"
   pull_request:
     types: [opened, synchronize, reopened]
-    paths:
-      - "modules"
-      - "Makefile"
-      - "scripts/**"
-      - "package/**"
-      - "patches/**"
-      - "targets/**"
-      - ".github/workflows/build-gluon.yml"
+
 jobs:
+  changed:
+    runs-on: ubuntu-latest
+    outputs:
+      targets: ${{ steps.filter.outputs.changes }}
+    steps:
+      - uses: actions/checkout@v2
+
+      # Filter targets based on changed files
+      - uses: dorny/paths-filter@v2
+        id: filter
+        with:
+          filters: .github/filters.yml
+
   build_firmware:
+    needs: changed
     strategy:
       fail-fast: false
       matrix:
-        target: [ath79-generic, ath79-nand, bcm27xx-bcm2708, bcm27xx-bcm2709, ipq40xx-generic, ipq806x-generic, lantiq-xrx200, lantiq-xway, mediatek-mt7622, mpc85xx-p1010, mpc85xx-p1020, ramips-mt7620, ramips-mt7621, ramips-mt76x8, rockchip-armv8, sunxi-cortexa7, x86-generic, x86-geode, x86-legacy, x86-64, bcm27xx-bcm2710, mvebu-cortexa9]
+        # Read back changd targets to create build matrix
+        target: ${{ fromJSON(needs.changed.outputs.targets) }}
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
+
       - name: Install Dependencies
         run: sudo contrib/actions/install-dependencies.sh
+
       - name: Build
         run: contrib/actions/run-build.sh ${{ matrix.target }}
+
       - name: Archive build logs
         if: ${{ !cancelled() }}
         uses: actions/upload-artifact@v1
         with:
           name: ${{ matrix.target }}_logs
           path: openwrt/logs
+
       - name: Archive build output
         uses: actions/upload-artifact@v1
         with:
diff --git a/.github/workflows/check-patches.yml b/.github/workflows/check-patches.yml
index 2aeb09fdb9b86598f1a68a6ed62b6f077972d08d..f9cb6f112c771829cfe47054bd95bf0bcc396eaf 100644
--- a/.github/workflows/check-patches.yml
+++ b/.github/workflows/check-patches.yml
@@ -17,7 +17,7 @@ jobs:
     name: Check patches
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
       - name: Refresh patches
         run: make refresh-patches GLUON_SITEDIR="contrib/ci/minimal-site"
       - name: Show diff
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index d079b2352ceb9dc8558e2645be8933e5a2c9abab..ebc5817e341748f8720964e04bef85afacb648c2 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -8,7 +8,7 @@ jobs:
     name: Lua
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
       - name: Install Dependencies
         run: sudo apt install lua-check
       - name: Install example site
@@ -20,7 +20,7 @@ jobs:
     name: Shell
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
       - name: Install Dependencies
         run: sudo apt install shellcheck
       - name: Install example site
diff --git a/Makefile b/Makefile
index 4ff9f0c6007de2e531133ce1c4aebf733a8f41d2..480691916659c3d92cc103a10b9a887b5d6b8a22 100644
--- a/Makefile
+++ b/Makefile
@@ -100,6 +100,8 @@ refresh-patches: FORCE
 update-feeds: FORCE
 	@$(GLUON_ENV) scripts/feeds.sh
 
+update-ci: FORCE
+	@scripts/update-ci.sh
 
 GLUON_TARGETS :=
 
diff --git a/contrib/actions/generate-actions.py b/contrib/actions/generate-actions.py
deleted file mode 100755
index 3b3ea8f25ffb06349bf4fffd17ecd8070718a247..0000000000000000000000000000000000000000
--- a/contrib/actions/generate-actions.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-
-ACTIONS_HEAD = """
-# Update this file after adding/removing/renaming a target by running
-# `make list-targets BROKEN=1 | ./contrib/actions/generate-actions.py > ./.github/workflows/build-gluon.yml`
-
-name: Build Gluon
-on:
-  push:
-    branches:
-      - master
-      - next*
-      - v20*
-    paths:
-      - "modules"
-      - "Makefile"
-      - "scripts/**"
-      - "package/**"
-      - "patches/**"
-      - "targets/**"
-      - ".github/workflows/build-gluon.yml"
-  pull_request:
-    types: [opened, synchronize, reopened]
-    paths:
-      - "modules"
-      - "Makefile"
-      - "scripts/**"
-      - "package/**"
-      - "patches/**"
-      - "targets/**"
-      - ".github/workflows/build-gluon.yml"
-jobs:
-  build_firmware:
-    strategy:
-      fail-fast: false
-      matrix:
-        target: [{matrix}]
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v1
-      - name: Install Dependencies
-        run: sudo contrib/actions/install-dependencies.sh
-      - name: Build
-        run: contrib/actions/run-build.sh ${{{{ matrix.target }}}}
-      - name: Archive build logs
-        if: ${{{{ !cancelled() }}}}
-        uses: actions/upload-artifact@v1
-        with:
-          name: ${{{{ matrix.target }}}}_logs
-          path: openwrt/logs
-      - name: Archive build output
-        uses: actions/upload-artifact@v1
-        with:
-          name: ${{{{ matrix.target }}}}_output
-          path: output
-"""
-
-targets = []
-
-for target in sys.stdin:
-    targets.append(target.strip())
-
-output = ACTIONS_HEAD.format(matrix=", ".join(targets))
-
-print(output)
diff --git a/contrib/actions/generate-target-filters.py b/contrib/actions/generate-target-filters.py
new file mode 100755
index 0000000000000000000000000000000000000000..084ce104fd7a402e35e490d82650af3db7d6bdd2
--- /dev/null
+++ b/contrib/actions/generate-target-filters.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+# Update target filters using
+#   make update-ci
+
+import sys
+import json
+
+# these changes trigger rebuilds on all targets
+common = [
+    "modules",
+    "Makefile",
+    "patches/**",
+    "targets/generic",
+    "targets/targets.mk",
+]
+
+# these changes are only built on x86-64
+extra = [
+    "contrib/ci/minimal-site/**",
+    "package/**"
+]
+
+_filter = dict()
+
+# construct filters map from stdin
+for target in sys.stdin:
+    target = target.strip()
+
+    _filter[target] = [
+        f"targets/{target}"
+    ] + common
+
+    if target == "x86-64":
+        _filter[target].extend(extra)
+
+# print filters to stdout in json format, because json is stdlib and yaml compatible.
+print(json.dumps(_filter, indent=2))
diff --git a/scripts/update-ci.sh b/scripts/update-ci.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cebc92dc039df7d64953f2c5973942154ff87183
--- /dev/null
+++ b/scripts/update-ci.sh
@@ -0,0 +1,3 @@
+#!/bin/sh -e
+
+make --no-print-directory list-targets BROKEN=1 | ./contrib/actions/generate-target-filters.py > .github/filters.yml