diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6301b5419b957f90b93210c7d766d96273a53d0d..428e8c63f6fcbd995ee1d7ecda2ffd12ceb2f4b7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,7 @@
-image: php:8.0-fpm
+image: thecodingmachine/php:8.0-v4-fpm-node14
 
 stages:
+  - prepare
   - quality
   - bundle
   - release
@@ -10,32 +11,31 @@ cache:
     - vendor/
     - node_modules/
 
-before_script:
-  - apt-get update -y
-
-  # Install git which is required by composer (the php image doesn't have it)
-  - apt-get install git -y
-
-  - apt-get install -y libicu-dev
-
-  # Install intl PHP extension for tests
-  - docker-php-ext-install intl
-
-  # Install composer
-  - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
-  - php composer-setup.php
-  - php -r "unlink('composer-setup.php');"
+php-dependencies:
+  stage: prepare
+  script:
+    # Install all php dependencies
+    - php composer.phar install --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs
 
-  # Install latest npm
-  - curl -sL https://deb.nodesource.com/setup_lts.x | bash -
-  - apt-get update && apt-get install -y nodejs
-  - npm install --global npm
+js-dependencies:
+  stage: prepare
+  script:
+    # Install all npm dependencies
+    - npm install
 
-  # Install all php dependencies
-  - php composer.phar install --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs
+lint-commit-msg:
+  stage: quality
+  script:
+    - chmod +x ./scripts/lint-commit.sh
+    # lint commit message
+    - ./scripts/lint-commit.sh
 
 tests:
   stage: quality
+  before_script:
+    # Install required intl PHP extension for tests
+    - apt-get install -y libicu-dev
+    - docker-php-ext-install intl
   script:
     # run phpunit without code coverage
     # TODO: add code coverage
@@ -44,20 +44,30 @@ tests:
 code-style:
   stage: quality
   script:
+    # check php code style
     - vendor/bin/ecs check --ansi
 
 static-analysis:
   stage: quality
   script:
-    # increase memory limit to 1GB because of script failure
+    # phpstan - increase memory limit to 1GB to prevent script failure
     - php -d memory_limit=1G vendor/bin/phpstan analyse --ansi
 
 code-review:
   stage: quality
   script:
+    # run rector to check for php errors
     - vendor/bin/rector process --dry-run --ansi
 
-bundle_app:
+lint-js:
+  stage: quality
+  script:
+    - npm run prettier
+    - npm run typecheck
+    - npm run eslint
+    - npm run stylelint
+
+bundle:
   stage: bundle
   script:
     # make scripts/bundle.sh executable
@@ -76,7 +86,7 @@ bundle_app:
     - beta
     - alpha
 
-release_app:
+release:
   stage: release
   script:
     # make release scripts executable
diff --git a/package.json b/package.json
index f11e6050d537fad48e64f39fc1c06f82f5b3dee8..92a7a2d230ffc448acac08fe4f2aaa21223c20a8 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
     "lint": "eslint --ext js,ts app/Views/_assets",
     "lint:fix": "eslint --ext js,ts app/Views/_assets --fix",
     "lint:css": "stylelint \"app/Views/_assets/**/*.css\"",
+    "prettier": "prettier --check --ignore-path .gitignore .",
     "prettier:fix": "prettier --write --ignore-path .gitignore .",
     "typecheck": "tsc",
     "commit": "git-cz",
diff --git a/scripts/bundle-prepare.sh b/scripts/bundle-prepare.sh
index d5aae79b99357db12152bf59a8ad2c974084f945..d75aea226d2b3938c71715557f559cab9aad8630 100644
--- a/scripts/bundle-prepare.sh
+++ b/scripts/bundle-prepare.sh
@@ -1,8 +1,7 @@
 #!/bin/bash
 
-# install only dev dependencies using the --no-dev option
+# install only production dependencies using the --no-dev option
 php composer.phar install --no-dev --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs
 
-# install js dependencies and build all production UI assets
-npm install
+# build all production UI assets
 npm run build
diff --git a/scripts/lint-commit.sh b/scripts/lint-commit.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7807276f832909805e79d2287be06c527c364689
--- /dev/null
+++ b/scripts/lint-commit.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# see https://github.com/conventional-changelog/commitlint/issues/885
+
+if [ "${CI_COMMIT_BEFORE_SHA}" = "0000000000000000000000000000000000000000" ]
+then
+    echo "commitlint from HEAD^"
+    npx commitlint --from=HEAD^
+else
+    echo "commitlint from ${CI_COMMIT_BEFORE_SHA}"
+    npx commitlint --from="${CI_COMMIT_BEFORE_SHA}"
+fi