diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4868ca4a151976f678844d6a094958e6290de1a3..8068f02bb4948c1d9e01756b3916eeb1d31f7e25 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,17 @@ variables:
   IMAGE_TAG_HELM_FILE: "helm/staging/graphpolaris-frontend/values.yaml"
   IMAGE_TAG_HELM_FILE_VARIABLE: "image.tag"
 
+stages:
+  - tag-release
+  - container-image
+  - deploy-staging
+
 include:
+  # Release needs to happen before build-and-push, because build-and-push
+  # searches for a tag that contains the current commit SHA.
+  - project: 'graphpolaris/pipelines'
+    ref: main
+    file: 'semantic-release.yml'
   - project: 'graphpolaris/pipelines'
     ref: main
     file: 'docker-build-push.yml'
diff --git a/.releaserc.json b/.releaserc.json
new file mode 100644
index 0000000000000000000000000000000000000000..18aa47a049846a933acbcd494f99a1f0c9840150
--- /dev/null
+++ b/.releaserc.json
@@ -0,0 +1,25 @@
+{
+  "branches": [
+    "master",
+    {
+      "name": "beta",
+      "prerelease": true
+    },
+    {
+      "name": "alpha",
+      "prerelease": true
+    }
+  ],
+  "dryRun": false,
+  "plugins": [
+    "@semantic-release/commit-analyzer",
+    "@semantic-release/release-notes-generator",
+    [
+      "@semantic-release/exec",
+      {
+        "successCmd": "echo ${nextRelease.version} > version.txt"
+      }
+    ],
+    "@semantic-release/gitlab"
+  ]
+}
diff --git a/package-semantic-release.json b/package-semantic-release.json
new file mode 100644
index 0000000000000000000000000000000000000000..8348eac1e1f140e19eaabb7a9323df235093b687
--- /dev/null
+++ b/package-semantic-release.json
@@ -0,0 +1,9 @@
+{
+  "dependencies": {
+    "semantic-release": "^21.0.0",
+    "@semantic-release/exec": "^6.0.0",
+    "@semantic-release/commit-analyzer": "^10.0.0",
+    "@semantic-release/gitlab": "^12.0.0",
+    "@semantic-release/release-notes-generator": "^11.0.0"
+  }
+}