diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index b78c3a3..0000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-version: 2.1
-orbs:
-  ktlint: idanelh/ktlint@1.0.1
-executors:
-  jdk8:
-    docker:
-      - image: cimg/openjdk:8.0
-commands:
-  run_gradle:
-    parameters:
-      gradlew_binary:
-        type: string
-        default: ./gradlew
-      tasks:
-        description: Gradle tasks to run
-        type: string
-        default: NONE
-    steps:
-      - run:
-          name: Run Gradle
-          command: |
-            << parameters.gradlew_binary >> \
-              --info \
-              --stacktrace \
-              --console=plain \
-              --no-daemon \
-              << parameters.tasks >>
-jobs:
-  test:
-    executor: jdk8
-    steps:
-      - checkout
-      - run_gradle:
-          tasks: clean test
-      - run:
-          name: Save test results
-          command: |
-            mkdir -p ~/test-results/junit/
-            find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \;
-            curl -Os https://uploader.codecov.io/latest/linux/codecov
-            chmod +x codecov
-            ./codecov -t ${CODECOV_TOKEN}
-          when: always
-      - store_test_results:
-          path: ~/test-results
-      - store_artifacts:
-          path: ~/test-results/junit
-
-workflows:
-  version: 2
-  build:
-    jobs:
-      - test
-      - ktlint/lint:
-          name: lint_shapeshift
-          working-directory: shapeshift
-      - ktlint/lint:
-          name: lint_shapeshift_spring_boot_starter
-          working-directory: spring-boot-starter-shapeshift
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..152bcd1
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,52 @@
+name: Publish
+
+on:
+  push:
+    branches:
+      - master
+    tags:
+      - v*
+  pull_request:
+    branches:
+      - master
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out Git repository
+        uses: actions/checkout@v3
+      - name: Install Java
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: '17'
+      - name: Cache Gradle packages
+        uses: actions/cache@v3
+        with:
+          path: ~/.gradle/caches
+          key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
+          restore-keys: ${{ runner.os }}-gradle
+      - name: Build and publish
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+          OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
+          OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
+          OSSRH_GPG_SECRET_KEY_BASE64: ${{ secrets.OSSRH_GPG_SECRET_KEY_BASE64 }}
+          OSSRH_GPG_SECRET_KEY_PASSWORD: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
+        run: |
+          if [ "${{ github.event_name }}" = "pull_request" ]; then
+            export RELEASE_VERSION=${{ github.event.number }}-PR-SNAPSHOT
+            echo "Deploying version $RELEASE_VERSION"
+            ./gradlew -Prelease jar publishToSonatype
+          else
+            if [ "${{ github.ref_type }}" = "tag" ]; then
+              export RELEASE_VERSION=${{ github.ref_name }}
+              export RELEASE_VERSION=${RELEASE_VERSION:1}
+            else
+              export RELEASE_VERSION=${{ github.sha }}-SNAPSHOT
+            fi
+            echo "Deploying version $RELEASE_VERSION"
+            ./gradlew -Prelease jar publishToSonatype closeAndReleaseSonatypeStagingRepository
+          fi
+          
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..8122204
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,33 @@
+name: Release
+on:
+  push:
+    branches:
+      - master
+      - beta
+
+permissions:
+  contents: read # for checkout
+
+jobs:
+  release:
+    name: Release
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write # to be able to publish a GitHub release
+      issues: write # to be able to comment on released issues
+      pull-requests: write # to be able to comment on released pull requests
+      id-token: write # to enable use of OIDC for npm provenance
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+          token: ${{ secrets.CLONE_TOKEN }}
+      - name: Setup Node.js
+        uses: actions/setup-node@v3
+        with:
+          node-version: "lts/*"
+      - name: Release
+        env:
+          GITHUB_TOKEN: ${{ secrets.CLONE_TOKEN }}
+        run: npx semantic-release
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..4c70771
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,44 @@
+name: Test
+
+on:
+  push:
+    branches:
+      - master
+    tags:
+      - v*
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out Git repository
+        uses: actions/checkout@v3
+      - name: Install Java
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: '17'
+      - name: Cache SonarCloud packages
+        uses: actions/cache@v3
+        with:
+          path: ~/.sonar/cache
+          key: ${{ runner.os }}-sonar
+          restore-keys: ${{ runner.os }}-sonar
+      - name: Cache Gradle packages
+        uses: actions/cache@v3
+        with:
+          path: ~/.gradle/caches
+          key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
+          restore-keys: ${{ runner.os }}-gradle
+      - name: Build and analyze
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: ./gradlew test --info
+      - name: Publish Test Report
+        uses: mikepenz/action-junit-report@v3
+        if: success() || failure()
+        with:
+          report_paths: '**/build/test-results/test/TEST-*.xml'
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 0decfeb..ce83fd8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,52 +1,60 @@
 import org.jetbrains.dokka.gradle.DokkaTask
+import java.util.*
 
 plugins {
     `java-library`
     `maven-publish`
     signing
     id("org.jetbrains.dokka") version "1.6.0"
+    id("io.github.gradle-nexus.publish-plugin") version "2.0.0"
 }
 
 if (hasProperty("release")) {
-    val releaseVersion = extra["shapeshift.version"].toString()
-    subprojects {
-        apply(plugin = "java-library")
-        apply(plugin = "signing")
-        apply(plugin = "maven-publish")
-        apply(plugin = "org.jetbrains.dokka")
+    val ossrhUsername = System.getenv("OSSRH_USERNAME")
+    val ossrhPassword = System.getenv("OSSRH_PASSWORD")
+    val releaseVersion = System.getenv("RELEASE_VERSION")
+    group = "dev.krud"
+    version = releaseVersion
+    nexusPublishing {
+        this@nexusPublishing.repositories {
+            sonatype {
+                username.set(ossrhUsername)
+                password.set(ossrhPassword)
+                nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
+                snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
+            }
+        }
+    }
+}
+
+allprojects {
+    repositories {
+        mavenLocal()
+        mavenCentral()
+    }
+    apply(plugin = "java-library")
+    apply(plugin = "maven-publish")
+    apply(plugin = "signing")
+
+    if (hasProperty("release")) {
+        val releaseVersion = System.getenv("RELEASE_VERSION")
+        val signingKeyBase64 = System.getenv("OSSRH_GPG_SECRET_KEY_BASE64")
+        val signingPassword = System.getenv("OSSRH_GPG_SECRET_KEY_PASSWORD")
         group = "dev.krud"
         version = releaseVersion
-        java.sourceCompatibility = JavaVersion.VERSION_1_8
         val isSnapshot = version.toString().endsWith("-SNAPSHOT")
-        val repoUri = if (isSnapshot) {
-            "https://s01.oss.sonatype.org/content/repositories/snapshots/"
-        } else {
-            "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
-        }
-
-        if (!isSnapshot) {
-            java {
-                withJavadocJar()
-                withSourcesJar()
-            }
+        java {
+            withJavadocJar()
+            withSourcesJar()
         }
 
         publishing {
             publications.create<MavenPublication>("maven") {
                 from(components["java"])
-                version = releaseVersion
-                repositories {
-                    maven {
-                        name = "OSSRH"
-                        url = uri(repoUri)
-                        credentials {
-                            username = System.getenv("OSSRH_USERNAME") ?: extra["ossrh.username"]?.toString()
-                            password = System.getenv("OSSRH_PASSWORD") ?: extra["ossrh.password"]?.toString()
-                        }
-                    }
-                }
+                artifactId = project.name
+
                 pom {
-                    name.set(this@subprojects.name)
+                    name.set(project.name)
                     description.set("A Kotlin library for intelligent object mapping.")
                     url.set("https://github.com/krud-dev/shapeshift/shapeshift")
                     licenses {
@@ -74,17 +82,22 @@ if (hasProperty("release")) {
             }
         }
 
-        if (!isSnapshot) {
-            val javadocTask = tasks.named<Javadoc>("javadoc").get()
-
-            tasks.withType<DokkaTask> {
-                javadocTask.dependsOn(this)
-                outputDirectory.set(javadocTask.destinationDir)
-            }
+        val javadocTask = tasks.named<Javadoc>("javadoc").get()
 
-            signing {
-                sign(publishing.publications["maven"])
-            }
+        tasks.withType<DokkaTask> {
+            javadocTask.dependsOn(this)
+            outputDirectory.set(javadocTask.destinationDir)
+        }
+        signing {
+            val signingKey = signingKeyBase64?.let { decodeBase64(it) }
+            useInMemoryPgpKeys(
+                signingKey, signingPassword
+            )
+            sign(publishing.publications["maven"])
         }
     }
-}
\ No newline at end of file
+}
+
+fun decodeBase64(base64: String): String {
+    return String(Base64.getDecoder().decode(base64))
+}