diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 06eab896e20e061bf7b09855df7478a07f662f28..f3c7a7ab43936ac4b71aba2e6080eb77b20885b9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,29 +4,31 @@ stages:
   - build
   - deploy
 
+# Prevent detached merge request pipelines
+# https://docs.gitlab.com/ee/ci/merge_request_pipelines/
+# Those do not run the environment on_stop jobs when MRs are closed/merged
+# or when branches get deleted, unlike branch pipelines.
+workflow:
+  rules:
+    - if: '$CI_MERGE_REQUEST_ID'
+      when: never
+    - when: always
+
 before_script:
   - npm ci
 
-.test:
-  stage: test
-  only:
-    # Default "only" + merge_requests
-    - branches
-    - tags
-    - merge_requests
-
 frontend-test:
-  extends: .test
+  stage: test
   script:
     - npm run test
 
 frontend-lint:
-  extends: .test
+  stage: test
   script:
     - npm run lint
 
 frontend-audit:
-  extends: .test
+  stage: test
   script:
     - npm audit --parseable
   allow_failure: true
@@ -45,11 +47,14 @@ frontend-audit:
   script:
     - npm run production
 
-frontend-build-mr:
+frontend-build-surge:
   extends: .build
 
-  only:
-    - merge_requests
+  # Only deploy branches on surge
+  rules:
+    - if: '$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "master"'
+      when: on_success
+    - when: never
 
   variables:
     # On merge requests we build using preprod to be able
@@ -60,46 +65,57 @@ frontend-build-mr:
 frontend-build:
   extends: .build
 
-  except:
-    - merge_requests
+  # Build only for master and tags
+  rules:
+    - if: '$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "master"'
+      when: never
+    - when: on_success
 
   variables:
     # On default branches we build using a relative API Url
     API_BASE_URL: /api/v1
 
-deploy-surge:
+.surge:
   stage: deploy
 
-  # Only deploy merge requests on surge
-  only:
-    - merge_requests
-
   environment:
     name: ${CI_COMMIT_REF_SLUG}
-    url: https://${CI_COMMIT_REF_SLUG}-teklia.surge.sh
-    on_stop: stop-surge
 
   before_script:
     - npm install -g surge
 
+deploy-surge:
+  extends: .surge
+
+  # Only deploy branches on surge
+  rules:
+    - if: '$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "master"'
+      when: on_success
+    - when: never
+
+  environment:
+    url: https://${CI_COMMIT_REF_SLUG}-teklia.surge.sh
+    on_stop: stop-surge
+
   script:
     - surge dist ${CI_ENVIRONMENT_URL}
 
 stop-surge:
-  stage: deploy
-  when: manual
+  extends: .surge
+
+  # Only deploy branches on surge
+  rules:
+    - if: '$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "master"'
+      when: manual
+      allow_failure: true
+    - when: never
 
   # Do not try to checkout the branch if it was deleted
   variables:
     GIT_STRATEGY: none
 
   environment:
-    name: ${CI_COMMIT_REF_SLUG}
-    url: https://${CI_COMMIT_REF_SLUG}-teklia.surge.sh
     action: stop
 
-  before_script:
-    - npm install -g surge
-
   script:
     - surge teardown ${CI_ENVIRONMENT_URL}