diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 28529bb82c95704b75cfb20bacc36abda82480e5..d3b9dfa1766defff0989091e3b52aa6d56016b05 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,12 @@
 stages:
   - build
   - test
+  - upload
+  - release
+
+variables:
+  LINUX_AMD64_BINARY: "shift-linux-amd64-${CI_COMMIT_TAG}"
+  PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/shift/${CI_COMMIT_TAG}"
 
 stack-build:
   stage: build
@@ -14,6 +20,13 @@ stack-build:
     - export STACK_ROOT="$CI_PROJECT_DIR/.stack"
   script:
     - stack --no-terminal build
+    - mkdir bin
+    - bash -c "cp $(stack path --local-install-root)/bin/shift bin/"
+  artifacts:
+    untracked: false
+    expire_in: 30 days
+    paths:
+      - bin/
 
 stack-test:
   stage: test
@@ -28,3 +41,22 @@ stack-test:
     - export STACK_ROOT="$CI_PROJECT_DIR/.stack"
   script:
     - stack --no-terminal test
+
+upload:
+  stage: upload
+  image: curlimages/curl:latest
+  rules:
+    - if: $CI_COMMIT_TAG
+  script:
+    - |
+      curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file bin/shift ${PACKAGE_REGISTRY_URL}/${LINUX_AMD64_BINARY}
+
+release:
+  stage: release
+  image: registry.gitlab.com/gitlab-org/release-cli:latest
+  rules:
+    - if: $CI_COMMIT_TAG
+  script:
+    - |
+      release-cli create --name "Release $CI_COMMIT_TAG" --tag-name $CI_COMMIT_TAG \
+        --assets-link "{\"name\":\"${LINUX_AMD64_BINARY}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_AMD64_BINARY}\"}"