diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c628ff28fff3cc14a658067aa34c32d1ae2563e8
--- /dev/null
+++ b/.github/actions/setup/action.yml
@@ -0,0 +1,19 @@
+name: Setup
+
+runs:
+  using: composite
+  steps:
+    - name: Install node
+      uses: actions/setup-node@v1
+      with:
+        node-version: 16
+    - name: Cache
+      uses: actions/cache@v2
+      id: cache
+      with:
+        path: '**/node_modules'
+        key: yarn-v1-${{ hashFiles('**/yarn.lock') }}
+    - name: Install
+      run: yarn --immutable
+      shell: bash
+      if: steps.cache.outputs.cache-hit != 'true'
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index fac7559ff9d966bd980f013d93e839727af71854..1a308221ba623225f8bfb48b30130d2da6b5fc5e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -19,19 +19,8 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
-      - name: Install node
-        uses: actions/setup-node@v1
-        with:
-          node-version: 16
-      - name: Cache
-        uses: actions/cache@v2
-        id: cache
-        with:
-          path: '**/node_modules'
-          key: yarn-v1-${{ hashFiles('**/yarn.lock') }}
-      - name: Install
-        run: yarn --immutable
-        if: steps.cache.outputs.cache-hit != 'true'
+      - name: Set up environment
+        uses: ./.github/actions/setup
       - name: Build API
         run: cd packages/loot-core && yarn build:api
 
@@ -39,19 +28,8 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
-      - name: Install node
-        uses: actions/setup-node@v1
-        with:
-          node-version: 16
-      - name: Cache
-        uses: actions/cache@v2
-        id: cache
-        with:
-          path: '**/node_modules'
-          key: yarn-v1-${{ hashFiles('**/yarn.lock') }}
-      - name: Install
-        run: yarn --immutable
-        if: steps.cache.outputs.cache-hit != 'true'
+      - name: Set up environment
+        uses: ./.github/actions/setup
       - name: Build Web
         run: ./bin/package-browser
 
@@ -67,18 +45,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
       - uses: actions/checkout@v2
-      - name: Install node
-        uses: actions/setup-node@v1
-        with:
-          node-version: 16
-      - name: Cache
-        uses: actions/cache@v2
-        id: cache
-        with:
-          path: '**/node_modules'
-          key: yarn-v1-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
-      - name: Install
-        run: yarn --immutable
-        if: steps.cache.outputs.cache-hit != 'true'
+      - name: Set up environment
+        uses: ./.github/actions/setup
       - name: Build Electron
         run: ./bin/package
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 3a1ccb71356498dbdc1276e522bc5a431d684e09..10fc5f3bb027d915c4318e6f2ebf9994f1966c28 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -12,18 +12,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
-      - name: Install node
-        uses: actions/setup-node@v1
-        with:
-          node-version: 16
-      - name: Cache
-        uses: actions/cache@v2
-        id: cache
-        with:
-          path: '**/node_modules'
-          key: yarn-v1-${{ hashFiles('**/yarn.lock') }}
-      - name: Install
-        run: yarn --immutable
-        if: steps.cache.outputs.cache-hit != 'true'
+      - name: Set up environment
+        uses: ./.github/actions/setup
       - name: Lint
         run: yarn lint