A GitLab CI rules block replaces only/except

only and except could not be combined coherently — specifying both applied them independently and produced results nobody predicted.

deploy:production:
  stage: deploy
  script: [./deploy.sh]
  rules:
    - if: '$CI_COMMIT_BRANCH == "master" && $CI_PIPELINE_SOURCE == "push"'
      when: manual
      allow_failure: false
    - when: never

# rules are evaluated in order; the first match wins.
# the trailing 'when: never' is the explicit default.

First-match-wins ordering is what makes rules comprehensible where the old pair was not, and the explicit terminating rule is a habit worth adopting — without it the default when nothing matches is on_success, which is the opposite of what a deploy job wants. allow_failure: false on a manual job is what stops the pipeline being marked green while the deployment is still waiting for somebody to press the button.