diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 00000000..1252ae4a
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,102 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*' # e.g., v1.0.4, v1.0.4-SNAPSHOT
+
+concurrency:
+ group: release-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Remove any pre-existing Maven settings.xml
+ run: rm -f "$HOME/.m2/settings.xml" || true
+
+ - name: Set up Temurin JDK 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "17"
+ cache: maven
+
+ - name: Import GPG key
+ uses: crazy-max/ghaction-import-gpg@v6
+ with:
+ gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
+ passphrase: ${{ secrets.GPG_PASSPHRASE }}
+ git_user_signingkey: false
+
+ - name: Configure Maven settings for Sonatype OSSRH
+ run: |
+ mkdir -p "$HOME/.m2"
+ cat > "$HOME/.m2/settings.xml" <<'XML'
+
+ /home/runner/.m2/repository
+ false
+
+
+ central
+ ${env.CENTRAL_USERNAME}
+ ${env.CENTRAL_PASSWORD}
+
+
+
+ XML
+
+ - name: Extract version from tag
+ id: version
+ run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
+
+ - name: Determine release type (snapshot vs production)
+ id: reltype
+ run: |
+ if [[ "${GITHUB_REF}" == *"-SNAPSHOT" ]]; then
+ echo "TYPE=snapshot" >> "$GITHUB_OUTPUT"
+ elif [[ "${GITHUB_REF}" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "TYPE=production" >> "$GITHUB_OUTPUT"
+ else
+ echo "ERROR: unexpected tag format '${GITHUB_REF}'. Expected vX.Y.Z or vX.Y.Z-SNAPSHOT." >&2
+ exit 1
+ fi
+
+ - name: Set project version from tag
+ run: |
+ mvn -B -ntp versions:set -DnewVersion="${{ steps.version.outputs.VERSION }}" -DprocessAllModules=true
+ mvn -B -ntp versions:commit
+
+ - name: Build & Test (signs at verify)
+ run: mvn -B -ntp clean verify -P release
+ env:
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+
+ - name: Deploy to Sonatype OSSRH
+ run: mvn -B -ntp deploy -DskipTests -P release
+ env:
+ CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
+ CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ github.ref_name }}
+ draft: false
+ prerelease: ${{ steps.reltype.outputs.TYPE == 'snapshot' }}
+ generate_release_notes: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index 6d550ac5..f9454c11 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,42 @@ public class Test {
This is the hello world of Easy Rules. You can find other examples like the [Shop](https://github.com/opensrp/easy-rules/wiki/shop), [Airco](https://github.com/opensrp/easy-rules/wiki/air-conditioning) or [WebApp](https://github.com/opensrp/easy-rules/wiki/web-app) tutorials in the wiki.
+## Publishing
+
+Artifacts are published to [Maven Central](https://central.sonatype.com/) automatically when a tag is pushed to the repository.
+
+### Tag conventions
+
+| Type | Pattern | Example | Behaviour |
+|------|---------|---------|-----------|
+| Release | `v..` | `v4.1.1` | Published to Maven Central; GitHub Release created |
+| Snapshot | `v..-SNAPSHOT` | `v4.1.2-SNAPSHOT` | Published to the snapshots repository; GitHub Release marked as pre-release |
+
+```bash
+# cut a release
+git tag v4.1.1 && git push origin v4.1.1
+
+# cut a snapshot
+git tag v4.1.2-SNAPSHOT && git push origin v4.1.2-SNAPSHOT
+```
+
+The workflow (`publish.yml`) will:
+1. Set the project version from the tag (strips the `v` prefix)
+2. Run `mvn clean verify` — compiles and tests the project
+3. Run `mvn deploy` — uploads artifacts to Sonatype OSSRH (with release promotion/asset signing handled by the workflow's Maven configuration)
+4. Create a GitHub Release automatically
+
+### Required repository secrets
+
+The following secrets must be configured in **Settings → Secrets and variables → Actions**:
+
+| Secret | Description |
+|--------|-------------|
+| `CENTRAL_USERNAME` | Sonatype Central portal username |
+| `CENTRAL_PASSWORD` | Sonatype Central portal token |
+| `GPG_PRIVATE_KEY` | Armored GPG private key (`gpg --armor --export-secret-keys `) |
+| `GPG_PASSPHRASE` | Passphrase for the GPG key |
+
## Contribution
You are welcome to contribute to the project with pull requests on GitHub.
diff --git a/pom.xml b/pom.xml
index e4a62983..9b1d8475 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,8 @@
3.13.0
3.2.5
3.6.3
+ 3.3.1
+ 3.2.7
4.5
3.4.1
@@ -72,6 +74,12 @@
Lead developer
+
+ Ona Systems
+ info@opensrp.io
+ Ona Systems
+ https://ona.io
+
@@ -212,4 +220,63 @@
+
+
+ release
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven.source.plugin.version}
+
+
+ attach-sources
+ package
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven-javadoc-plugin.version}
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven.gpg.plugin.version}
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+
+