diff --git a/.github/workflows/fossa.yaml b/.github/workflows/fossa.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..87bfe9a58e9565975006c2cec92b02f4af7bd964
--- /dev/null
+++ b/.github/workflows/fossa.yaml
@@ -0,0 +1,34 @@
+name: FOSSA Scanning
+
+on:
+  push:
+    branches: ["main", "master", "release/**"]
+  # For manual scans.
+  workflow_dispatch:
+
+permissions:
+  # Basic read access needed.
+  contents: read
+  # Required to authenticate in EIO's Vault.
+  id-token: write
+
+jobs:
+  fossa-scanning:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v4
+
+    - name: Read FOSSA token
+      uses: rancher-eio/read-vault-secrets@main
+      with:
+        secrets: |
+          secret/data/github/org/rancher/fossa/push token | FOSSA_API_KEY_PUSH_ONLY
+
+    - name: FOSSA scan
+      uses: fossas/fossa-action@main
+      with:
+        api-key: ${{ env.FOSSA_API_KEY_PUSH_ONLY }}
+        # Only runs the scan and do not provide/return any results back to the
+        # pipeline.
+        run-tests: false
\ No newline at end of file
diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..735fc54e70ff78b230b2b52ab36b2dd5ca1647cf
--- /dev/null
+++ b/.github/workflows/pull-request.yaml
@@ -0,0 +1,45 @@
+name: CI on Pull Request
+
+# The jobs below will execute any time a PR is created.
+on: 
+  push:
+    pull_request:
+
+jobs:
+  # Runs e2e tests.
+  build-test:
+    runs-on: ubuntu-latest
+    container: rancher/dapper:v0.6.0
+    permissions:
+      contents: read
+    strategy:
+      matrix:
+        os: [linux]
+        arch: [amd64, arm64]
+    steps:
+      - name: Add Git
+        run: apk add -U git
+
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Fix the not-a-git-repository issue
+        run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Set environment variables
+        run: echo "DAPPER_HOST_ARCH=${{ matrix.arch }}" >> $GITHUB_ENV
+
+      - name: Run CI
+        run: dapper ci
+      
+      - name: Run e2e
+        if: ${{ matrix.arch == 'amd64' }}
+        run: |
+          dapper e2e-sonobuoy
+          dapper e2e-verify
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2bed8a438a174430910c5a2134751904a8cbde16
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,227 @@
+name: CI on Release Tag
+
+# The jobs below will execute any time a tag is pushed to any branch in the repo
+# and when master is updated.
+on: 
+  push:
+    branches:
+      - 'master'
+    tags:
+      - '*'
+
+env:
+  IMAGE: rancher/system-upgrade-controller
+  TAG: ${{ github.ref_name }}
+
+jobs:
+  # Runs e2e tests and uploads the artifact files that Dapper generates to 
+  # GitHub Actions so we can reference them when we create the GitHub release.
+  build-test:
+    runs-on: ubuntu-latest
+    container: rancher/dapper:v0.6.0
+    permissions:
+      contents: read
+    strategy:
+      matrix:
+        os: [linux]
+        arch: [amd64, arm64]
+    steps:
+      - name: Add Git
+        run: apk add -U git
+
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Fix the not-a-git-repository issue
+        run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Set environment variables
+        run: echo "DAPPER_HOST_ARCH=${{ matrix.arch }}" >> $GITHUB_ENV
+
+      - name: Run CI
+        run: dapper ci
+      
+      - name: Run e2e
+        if: ${{ matrix.arch == 'amd64' }}
+        run: |
+          dapper e2e-sonobuoy
+          dapper e2e-verify
+
+      - name: Generate artifact checksums
+        run: find dist/artifacts -type f -exec sha256sum {} \; > "dist/artifacts/sha256sum-${{ matrix.arch }}.txt"
+
+      - name: Upload artifacts
+        uses: actions/upload-artifact@v4
+        with:
+          name: "artifacts-${{ matrix.os }}-${{ matrix.arch }}"
+          path: dist/artifacts/*
+          if-no-files-found: error
+          overwrite: true
+
+  # Creates a GitHub release using artifacts from the `build-test` job.
+  create-gh-release:
+    runs-on: ubuntu-latest
+    needs: 
+      - build-test
+    permissions:
+      contents: write # needed for creating the GH release
+    env:
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Fix the not-a-git-repository issue
+        run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
+
+      - name: Download artifacts
+        uses: actions/download-artifact@v4
+        with:
+          path: dist/artifacts
+          pattern: artifacts-*
+          merge-multiple: true
+
+      - name: Create GitHub release
+        run: gh release create "${{ env.TAG }}" --prerelease --title "${{ env.TAG }}" dist/artifacts/*
+
+  # Builds Docker images using artifacts from the `build-test` job and pushes 
+  # them to DockerHub.
+  build-push-images:
+    runs-on: ubuntu-latest
+    needs: 
+      - build-test
+    permissions:
+      contents: read
+      id-token: write # needed for the Vault authentication
+    strategy:
+      matrix:
+        os: [linux]
+        arch: [amd64, arm64]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.IMAGE }}
+          flavor: latest=false
+          tags: |
+            type=schedule
+            type=ref,event=branch
+            type=ref,event=tag
+            type=ref,event=pr
+            type=raw,value={{ tag }}-${{ matrix.arch }}
+
+      - name: Fix the not-a-git-repository issue
+        run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Load Secrets from Vault
+        uses: rancher-eio/read-vault-secrets@main
+        with:
+          secrets: |
+            secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ;
+            secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD
+
+      - name: Login to DockerHub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ env.DOCKER_USERNAME }}
+          password: ${{ env.DOCKER_PASSWORD }}
+
+      - name: Download artifacts
+        uses: actions/download-artifact@v4
+        with:
+          path: dist/artifacts
+          pattern: artifacts-*
+          merge-multiple: true
+
+      - name: Build and push controller image to DockerHub
+        uses: docker/build-push-action@v5
+        id: build
+        with:
+          context: .
+          file: package/Dockerfile
+          target: controller
+          build-args: |
+            ARCH=${{ matrix.arch }}
+          push: true
+          tags: "${{ steps.meta.outputs.tags }}"
+          platforms: "${{ matrix.os }}/${{ matrix.arch }}"
+          labels: "${{ steps.meta.outputs.labels }}"
+
+      - name: Export digest
+        run: |
+          mkdir -p /tmp/digests
+          digest="${{ steps.build.outputs.digest }}"
+          touch "/tmp/digests/${digest#sha256:}"
+      
+      - name: Upload digest
+        uses: actions/upload-artifact@v4
+        with:
+          name: "digests-${{ matrix.os }}-${{ matrix.arch }}"
+          path: /tmp/digests/*
+          if-no-files-found: error
+          overwrite: true
+
+  # Creates a manifest for the images built in the `build-push-images` job
+  # and pushes it to DockerHub.
+  build-push-manifest:
+    runs-on: ubuntu-latest
+    needs:
+      - build-push-images
+    permissions:
+      id-token: write # needed for the Vault authentication
+    steps:
+      - name: Download digests
+        uses: actions/download-artifact@v4
+        with:
+          path: /tmp/digests
+          pattern: digests-*
+          merge-multiple: true
+      
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+      
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.IMAGE }}
+          flavor: latest=false
+
+      - name: Load Secrets from Vault
+        uses: rancher-eio/read-vault-secrets@main
+        with:
+          secrets: |
+            secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ;
+            secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD
+      
+      - name: Login to Docker Hub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ env.DOCKER_USERNAME }}
+          password: ${{ env.DOCKER_PASSWORD }}
+      
+      - name: Create manifest list and push
+        working-directory: /tmp/digests
+        run: |
+          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+            $(printf '${{ env.IMAGE }}@sha256:%s ' *)          
+      
+      - name: Inspect image
+        run: |
+          docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }}          
\ No newline at end of file