diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1210b9963ef7616dc6ba191498f1ae5019b71c06..f257c7b91a5fc6c6678da8a4f851609f68d97513 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -48,9 +48,6 @@ stages:
 .prepare:
   stage: prepare
   image: python:3.7-stretch
-  script:
-    - python3 scripts/update-version.py -k "$SSH_TAGGING_KEY" -o version -v
-    - bash scripts/build.sh --version "$(cat version)"
   artifacts:
     paths:
       - version
@@ -66,10 +63,16 @@ prepare:dev:
 
 prepare:beta:
   extends: .prepare
+  script:
+    - python3 scripts/update-version.py -k "$SSH_TAGGING_KEY" -i build -f build -o version -v
+    - bash scripts/build.sh --version "$(cat version)"
   <<: *beta_rules
 
 prepare:release:
   extends: .prepare
+  script:
+    - python3 scripts/update-version.py -k "$SSH_TAGGING_KEY" -i patch -f patch -o version -v
+    - bash scripts/build.sh --version "$(cat version)"
   <<: *release_rules
 
 
@@ -234,13 +237,14 @@ pages:
       - public/
 
 
-
 # Upload and release
 
 upload:
   stage: collect
   image: curlimages/curl:latest
   script:
+    - VERSION=$(cat version)
+    - PACKAGE_REGISTRY_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/${VERSION}"
     - |
       curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file safelinks-cleaner-firefox-release.xpi ${PACKAGE_REGISTRY_URL}/
     - |
diff --git a/scripts/update-version.py b/scripts/update-version.py
index a201c34e1a6bbffaec2c24de684dfd708dd085f3..b2bd1e2e096fb47e6ffa15fa4786083df200e92d 100644
--- a/scripts/update-version.py
+++ b/scripts/update-version.py
@@ -27,12 +27,57 @@ _VERSION_REGEXP = re.compile(
     """,
     re.VERBOSE)
 
+_VERSION_FIELDS = ('major', 'minor', 'patch', 'build')
+_VERSION_FORMATS = {
+    field: '.'.join(f'{{{name}}}' for name in _VERSION_FIELDS[:_VERSION_FIELDS.index(field) + 1])
+    for field in _VERSION_FIELDS
+    }
 
-class Version(list):
+
+class Version:
     """Class representing a version number."""
 
+    def __init__(self, major, minor, patch=0, build=0):
+        self.major = major
+        self.minor = minor
+        self.patch = patch
+        self.build = build
+
+    def increment(self, what, amount):
+        if amount <= 0:
+            return self
+        if what == 'major':
+            self.major += amount
+            self.minor = 0
+            self.patch = 0
+            self.build = 0
+        elif what == 'minor':
+            self.minor += amount
+            self.patch = 0
+            self.build = 0
+        elif what == 'patch':
+            self.patch += amount
+            self.build = 0
+        elif what == 'build':
+            self.build += amount
+        else:
+            raise AttributeError(what)
+        return self
+
+    def _tuple(self):
+        return (self.major, self.minor, self.build, self.patch)
+
+    def __lt__(self, other):
+        return self._tuple() < other._tuple()
+
+    def format(self, fmt):
+        return _VERSION_FORMATS[fmt].format(major=self.major,
+                                            minor=self.minor,
+                                            patch=self.patch,
+                                            build=self.build)
+
     def __str__(self):
-        return '.'.join(str(part) for part in self)
+        return self.format('build')
 
 
 def parse_version(version):
@@ -40,10 +85,10 @@ def parse_version(version):
     match = _VERSION_REGEXP.match(version)
     if not match:
         raise ValueError(f'invalid version format: {version}')
-    return Version([int(match.group('major') or 0),
-                    int(match.group('minor') or 0),
-                    int(match.group('patch') or 0),
-                    int(match.group('build') or 0)])
+    return Version(int(match.group('major') or 0),
+                   int(match.group('minor') or 0),
+                   int(match.group('patch') or 0),
+                   int(match.group('build') or 0))
 
 
 def ssh_add_host_keys(host):
@@ -173,6 +218,11 @@ def main():
                         help='output version number to this file')
     parser.add_argument('--ssh-key', '-k', metavar='PATH',
                         help='file containing ssh key')
+    parser.add_argument('--format', '-f', choices=list(_VERSION_FORMATS.keys()), default='build',
+                        help="version number format")
+    parser.add_argument('--increment', '-i', choices=['major', 'minor', 'patch', 'build'],
+                        default='build',
+                        help="what to increment")
     parser.add_argument('--dry-run', '-n', action='store_true',
                         help="don't change anything")
     opts = parser.parse_args()
@@ -181,6 +231,10 @@ def main():
     if opts.verbose:
         _VERBOSE = True
 
+    if _VERSION_FIELDS.index(opts.increment) > _VERSION_FIELDS.index(opts.format):
+        fatal('incremented field not included in formatted version, exiting')
+        sys.exit(1)
+
     ssh_key = None
     if opts.ssh_key:
         with open(opts.ssh_key, 'r') as ssh_key_file:
@@ -228,11 +282,6 @@ def main():
         verbose(f'pipeline source is not push: not incrementing version')
         increment = 0
 
-    # Check branch
-    if commit_branch not in ('release', 'master'):
-        verbose(f'branch is not release or master: not incrementing')
-        increment = 0
-
     # Don't do anything if already tagged and at max
     commit_versions = get_commit_versions(commit_ref)
     all_versions = get_all_versions(commit_ref)
@@ -247,14 +296,8 @@ def main():
     except IndexError:
         pass
 
-    (major, minor, patch, build) = all_versions[0]
-    if commit_branch == 'release':
-        new_version = str(Version([major, minor, patch + increment]))
-    elif commit_branch == 'master':
-        new_version = str(Version([major, minor, patch, build + increment]))
-    else:
-        new_version = str(Version([major, minor, patch, build]))
-
+    verbose(f'increment: {opts.increment} by {increment}')
+    new_version = all_versions[0].increment(opts.increment, increment).format(opts.format)
 
     ssh_create_directory()
     ssh_add_host_keys(repository_host)