diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f4a77a94a3604fc8f35834d180dcb47a46d6e561..4a2bf8b518a893443110780cbe205eb3356a91bc 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -39,9 +39,10 @@ jobs:
           git config --global core.symlinks true
           git config --global user.email 'bot@renovateapp.com'
           git config --global user.name  'Renovate Bot'
-          node --version
+          yarn config set version-git-tag false
+          echo "Node $(node --version)"
           python --version
-          yarn --version
+          echo "Yarn $(yarn --version)"
           pip --version
 
       - uses: actions/checkout@v2
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 06333d31356e4c5039a2bf44db577ec9d1cbd71f..8253b868784630e928f580fb89020c7da13d04c0 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -63,9 +63,9 @@ jobs:
           git config --global core.symlinks true
           git config --global user.email 'bot@renovateapp.com'
           git config --global user.name  'Renovate Bot'
-          node --version
+          echo "Node $(node --version)"
           python --version
-          yarn --version
+          echo "Yarn $(yarn --version)"
           pip --version
 
       - uses: actions/checkout@v2
@@ -161,9 +161,9 @@ jobs:
           git config --global core.symlinks true
           git config --global user.email 'bot@renovateapp.com'
           git config --global user.name  'Renovate Bot'
-          node --version
+          echo "Node $(node --version)"
           python --version
-          yarn --version
+          echo "Yarn $(yarn --version)"
 
       - uses: actions/checkout@v2
 
diff --git a/.releaserc b/.releaserc
index 2f608f64c0cea093cd8e79bf990e0f34d1070f74..10e867e75d8e331cb74603a057542937252087ee 100644
--- a/.releaserc
+++ b/.releaserc
@@ -5,11 +5,10 @@
     [
       "@semantic-release/exec",
       {
-        "publishCmd": "./.github/workflows/release-docker.sh ${nextRelease.version} ${nextRelease.gitHead} && yarn dispatch-release ${nextRelease.version}"
+        "publishCmd": "yarn release --release=${nextRelease.version} --sha=${nextRelease.gitHead}"
       }
     ],
-    "@semantic-release/github",
-    "@semantic-release/npm"
+    "@semantic-release/github"
   ],
   "analyzeCommits": {
     "preset": "angular",
diff --git a/package.json b/package.json
index 933fc4899b63f85027ac34eb0c3b526ef10a0d3d..1d822153f813c67491163a10b0747b162c727552 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,6 @@
     "copy-static-files": "copyfiles -u 1 -e **/__fixtures__/**  -e **/__mocks__/** lib/**/*.json lib/**/*.py dist/",
     "create-json-schema": "babel-node --extensions \".ts,.js\" -- bin/create-json-schema.js && prettier --write \"renovate-schema.json\"",
     "debug": "babel-node --inspect-brk  --extensions \".ts,.js\" -- lib/renovate.ts",
-    "dispatch-release": "node --experimental-modules tools/dispatch-release.mjs",
     "eslint": "eslint --ext .js,.mjs,.ts lib/ test/ tools/",
     "eslint-fix": "eslint --ext .js,.mjs,.ts --fix lib/ test/ tools/",
     "jest": "cross-env NODE_ENV=test LOG_LEVEL=fatal node --expose-gc node_modules/jest/bin/jest.js",
@@ -29,6 +28,9 @@
     "prepare:re2": "node --experimental-modules tools/check-re2.mjs",
     "prettier": "prettier --list-different \"**/*.{ts,js,mjs,json,md}\"",
     "prettier-fix": "prettier --write \"**/*.{ts,js,mjs,json,md}\"",
+    "release": "run-s \"release:* {@}\" --",
+    "release:release": "node --experimental-modules tools/release.mjs",
+    "release:dispatch": "node --experimental-modules tools/dispatch-release.mjs",
     "start": "babel-node --extensions \".ts,.js\" -- lib/renovate.ts",
     "test-dirty": "git diff --exit-code",
     "test-e2e": "npm pack && cd e2e && yarn install --no-lockfile --ignore-optional --prod && yarn test",
diff --git a/tools/dispatch-release.mjs b/tools/dispatch-release.mjs
index acd61dd550a9266b11297fc0163dd34109b9c855..610465dde4444e27d02a96de0aa51e45e7a07020 100644
--- a/tools/dispatch-release.mjs
+++ b/tools/dispatch-release.mjs
@@ -1,9 +1,19 @@
 import got from 'got';
 import shell from 'shelljs';
+import program from './utils.mjs';
+
+const version = program.release;
+const dry = program.dryRun;
 
 const baseUrl = 'https://api.github.com/';
 
+shell.echo(`Dispatching version: ${version}`);
+
 (async () => {
+  if (dry) {
+    shell.echo('dry-run done.');
+    return;
+  }
   await got(`repos/${process.env.GITHUB_REPOSITORY}/dispatches`, {
     baseUrl,
     headers: {
@@ -18,6 +28,7 @@ const baseUrl = 'https://api.github.com/';
       client_payload: {
         sha: process.env.GITHUB_SHA,
         ref: process.env.GITHUB_REF,
+        version,
       },
     },
   });
diff --git a/tools/package.json b/tools/package.json
index bc96b581dedd8357fa7d5c68678c628f66ca27fa..41853d2eb18a6755c5476fe6757f1ad89363da56 100644
--- a/tools/package.json
+++ b/tools/package.json
@@ -2,7 +2,8 @@
   "private":true,
   "type": "module",
   "dependencies": {
-    "shelljs": "0.8.3",
-    "got": "9.6.0"
+    "commander": "4.1.1",
+    "got": "9.6.0",
+    "shelljs": "0.8.3"
   }
 }
diff --git a/tools/release.mjs b/tools/release.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..7f5af09f77100696f8d6986438ca966863ed2524
--- /dev/null
+++ b/tools/release.mjs
@@ -0,0 +1,32 @@
+import shell from 'shelljs';
+import program from './utils.mjs';
+
+const version = program.release;
+const sha = program.sha;
+const dry = program.dryRun;
+
+let err = false;
+
+shell.echo(`Publishing version: ${version}`);
+
+try {
+  shell.echo('Publishing docker images ...');
+  if (!dry)
+    shell.exec(`./.github/workflows/release-docker.sh ${version} ${sha}`);
+  else shell.echo('dry-run done.');
+} catch (e) {
+  shell.echo(e.toString());
+  err = true;
+}
+
+try {
+  shell.echo('Publishing npm package ...');
+  if (!dry)
+    shell.exec(`yarn publish --non-interactive --new-version ${version}`);
+  else shell.echo('dry-run done.');
+} catch (e) {
+  shell.echo(e.toString());
+  err = true;
+}
+
+if (err) shell.exit(1);
diff --git a/tools/utils.mjs b/tools/utils.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..e8d9c04582289943c754a5fbea1c757255ab3641
--- /dev/null
+++ b/tools/utils.mjs
@@ -0,0 +1,12 @@
+import commander from 'commander';
+
+const program = new commander.Command();
+program
+  .version('0.0.1')
+  .requiredOption('-r, --release <type>', 'Version to use')
+  .option('-s, --sha <type>', 'Sha to use')
+  .option('-d, --dry-run');
+
+program.parse(process.argv);
+
+export default program;