diff --git a/.drone.yml b/.drone.yml
deleted file mode 100644
index 14ab03777005e93c2ad3a4600ad7c34f5a35c388..0000000000000000000000000000000000000000
--- a/.drone.yml
+++ /dev/null
@@ -1,245 +0,0 @@
----
-kind: pipeline
-name: amd64
-
-platform:
-  os: linux
-  arch: amd64
-
-steps:
-- name: build
-  image: rancher/dapper:v0.6.0
-  commands:
-  - dapper ci
-  - dapper e2e-sonobuoy
-  - dapper e2e-verify
-  volumes:
-  - name: docker
-    path: /var/run/docker.sock
-
-- name: upload-artifacts
-  image: plugins/github-release
-  settings:
-    api_key:
-      from_secret: github_token
-    prerelease: true
-    checksum:
-    - sha256
-    checksum_file: CHECKSUMsum-amd64.txt
-    checksum_flatten: true
-    files:
-    - "dist/artifacts/*"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-
-- name: push-controller
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile
-    build_args:
-    - ARCH=amd64
-    - TAG=${DRONE_TAG}-amd64
-    password:
-      from_secret: docker_password
-    repo: "rancher/system-upgrade-controller"
-    tag: "${DRONE_TAG}-amd64"
-    username:
-      from_secret: docker_username
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-
-- name: push-e2e-tests
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile
-    build_args:
-    - ARCH=amd64
-    - TAG=${DRONE_TAG}-amd64
-    target: e2e-tests
-    password:
-      from_secret: docker_password
-    repo: "rancher/system-upgrade-controller"
-    tag: "${DRONE_TAG}-amd64-e2e-tests"
-    username:
-      from_secret: docker_username
-  when:
-    instance:
-      - drone-publish.rancher.io
-    ref:
-      - refs/head/master
-      - refs/tags/*
-    event:
-      - tag
-
-volumes:
-- name: docker
-  host:
-    path: /var/run/docker.sock
-
----
-kind: pipeline
-name: arm64
-
-platform:
-  os: linux
-  arch: arm64
-
-steps:
-- name: build
-  image: rancher/dapper:v0.6.0
-  commands:
-  - dapper ci
-  volumes:
-  - name: docker
-    path: /var/run/docker.sock
-
-- name: upload-artifacts
-  image: plugins/github-release
-  settings:
-    api_key:
-      from_secret: github_token
-    prerelease: true
-    checksum:
-    - sha256
-    checksum_file: CHECKSUMsum-arm64.txt
-    checksum_flatten: true
-    files:
-    - "dist/artifacts/*"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-
-- name: push-controller
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile
-    build_args:
-    - ARCH=arm64
-    - TAG=${DRONE_TAG}-arm64
-    password:
-      from_secret: docker_password
-    repo: "rancher/system-upgrade-controller"
-    tag: "${DRONE_TAG}-arm64"
-    username:
-      from_secret: docker_username
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-
-- name: push-e2e-tests
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile
-    build_args:
-    - ARCH=arm64
-    - TAG=${DRONE_TAG}-arm64
-    target: e2e-tests
-    password:
-      from_secret: docker_password
-    repo: "rancher/system-upgrade-controller"
-    tag: "${DRONE_TAG}-arm64-e2e-tests"
-    username:
-      from_secret: docker_username
-  when:
-    instance:
-      - drone-publish.rancher.io
-    ref:
-      - refs/head/master
-      - refs/tags/*
-    event:
-      - tag
-
-volumes:
-- name: docker
-  host:
-    path: /var/run/docker.sock
-
----
-kind: pipeline
-name: manifest
-
-platform:
-  os: linux
-  arch: amd64
-
-steps:
-- name: controller
-  image: plugins/manifest
-  settings:
-    username:
-      from_secret: docker_username
-    password:
-      from_secret: docker_password
-    platforms:
-      - linux/amd64
-      - linux/arm64
-    target: "rancher/system-upgrade-controller:${DRONE_TAG}"
-    template: "rancher/system-upgrade-controller:${DRONE_TAG}-ARCH"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-
-- name: e2e-tests
-  image: plugins/manifest
-  settings:
-    username:
-      from_secret: docker_username
-    password:
-      from_secret: docker_password
-    platforms:
-      - linux/amd64
-      - linux/arm64
-    target: "rancher/system-upgrade-controller:${DRONE_TAG}-e2e-tests"
-    template: "rancher/system-upgrade-controller:${DRONE_TAG}-ARCH-e2e-tests"
-  when:
-    instance:
-      - drone-publish.rancher.io
-    ref:
-      - refs/head/master
-      - refs/tags/*
-    event:
-      - tag
-
-depends_on:
-- amd64
-- arm64
-
----
-kind: pipeline
-name: fossa
-steps:
-- name: fossa
-  image: rancher/drone-fossa:latest
-  settings:
-    api_key:
-      from_secret: FOSSA_API_KEY
-  when:
-    instance:
-      - drone-publish.rancher.io
diff --git a/.github/workflows/fossa.yaml b/.github/workflows/fossa.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c5d34cdd91c1d7ba3f570b63a82afd8f78da2fff
--- /dev/null
+++ b/.github/workflows/fossa.yaml
@@ -0,0 +1,34 @@
+name: FOSSA Scanning
+
+on:
+  push:
+    branches: [ "master" ]
+  # 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..a558406b5204075561dcd210ce529da1f8a70c56
--- /dev/null
+++ b/.github/workflows/pull-request.yaml
@@ -0,0 +1,47 @@
+name: CI on Pull Request
+
+# The jobs below will execute any time a PR is created.
+on: 
+  pull_request:
+  push:
+    branches:
+      - 'master'
+
+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..ced4d9929e20fc057419ce0172e990b3bc1c86e6
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,224 @@
+name: CI on Release Tag
+
+# The jobs below will execute any time a tag is pushed to any branch in the repo.
+on:
+  push:
+    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