Gitlab Pipeline – except – What is except in GitLab CI/CD?

DevOps

MOTOSHARE 🚗🏍️
Turning Idle Vehicles into Shared Rides & Earnings

From Idle to Income. From Parked to Purpose.
Earn by Sharing, Ride by Renting.
Where Owners Earn, Riders Move.
Owners Earn. Riders Move. Motoshare Connects.

With Motoshare, every parked vehicle finds a purpose. Owners earn. Renters ride.
🚀 Everyone wins.

Start Your Journey with Motoshare

The except keyword in a job definition specifies a blacklist of conditions under which that job should not be created and run. If any of the conditions defined in except are met for a given pipeline trigger (like a push to a specific branch, a tag creation, etc.), the job will be skipped. If none of the except conditions are met, the job will be considered for creation (and will run if no only clause prevents it, or if it has no only clause).

Important Note: Just like with only, while except still functions, GitLab’s current best practice and strong recommendation is to use the rules: keyword instead. rules: offer significantly more power, flexibility, and clarity for controlling when jobs run or don’t run. This example is provided because you specifically asked about except. Please consider using rules: for your future and more complex pipeline configurations.


Example .gitlab-ci.yml with except:

YAML

# .gitlab-ci.yml

stages:
  - build
  - test
  - deploy
  - cleanup

default:
  image: alpine:latest

# Job 1: Runs on all branches EXCEPT 'main'
build_on_feature_branches:
  stage: build
  except:
    - main # Can also be written as refs: - main
  script:
    - echo "--- Building on a feature branch (not main) ---"
    - echo "Current branch: $CI_COMMIT_REF_NAME"

# Job 2: Runs for all pipeline triggers EXCEPT when Git tags are pushed
test_everything_but_tags:
  stage: test
  except:
    - tags # Special keyword for any tag
  script:
    - echo "--- Running tests (not for a tag) ---"
    - echo "Pipeline source: $CI_PIPELINE_SOURCE"
    - echo "This job does NOT run when a tag is pushed."

# Job 3: Runs for all pipeline triggers EXCEPT for merge request events
deploy_to_staging_not_for_mr:
  stage: deploy
  except:
    - merge_requests # Special keyword for merge request pipelines
  script:
    - echo "--- Deploying to Staging (not for an MR) ---"
    - echo "This job runs for direct pushes, tags, schedules, etc., but NOT for merge requests."

# Job 4: Runs for all pipeline triggers EXCEPT for scheduled pipelines
manual_cleanup_only:
  stage: cleanup
  except:
    - schedules # Special keyword for scheduled pipelines
  script:
    - echo "--- Manual Cleanup Task (not for schedules) ---"
    - echo "This job does NOT run for scheduled pipelines."

# Job 5: Excludes multiple specific branches
# This job runs unless the branch is 'develop' OR 'release/v1.0'
build_except_specific_branches:
  stage: build
  except:
    - develop
    - release/v1.0
  script:
    - echo "--- Building (excluding develop and release/v1.0) ---"
    - echo "Current branch is $CI_COMMIT_REF_NAME."

# Job 6: Runs always (no 'only' or 'except' clause)
# This job is included to show the default behavior.
always_run_job_default:
  stage: test
  script:
    - echo "--- Always Run Job (Default Behavior) ---"
    - echo "This job has no 'only' or 'except', so it runs for any pipeline trigger by default"
    - echo "(unless workflow:rules prevent the pipeline itself, or a more specific 'only' restricts it)."

# Job 7: Using 'refs' with 'except' (though 'rules' is better for this)
# This job does NOT run for branches starting with 'legacy-' OR for the 'archive' branch.
# Note: For complex logic, 'rules' is much clearer.
advanced_except_example:
  stage: cleanup
  except:
    refs:
      - /^legacy-.*$/ # Regex for branches starting with legacy-
      - archive
  script:
    - echo "--- Advanced Except Example ---"
    - echo "Running because this is not a legacy or archive branch."
    - echo "Branch: $CI_COMMIT_BRANCH"

Code language: PHP (php)

Explanation:

  1. except: Keyword:
    • Used within a job definition to specify conditions under which the job should not run. It acts as a blacklist.
    • The job will be skipped if any of the conditions listed under except are met for the event that triggered the pipeline.
  2. build_on_feature_branches Job:
    • except: - main: This job will be created and run for any branch push, unless the push is to the main branch.
  3. test_everything_but_tags Job:
    • except: - tags: This job will run for any pipeline trigger (branch pushes, merge requests, schedules, etc.) except when a Git tag is pushed.
  4. deploy_to_staging_not_for_mr Job:
    • except: - merge_requests: This job will run for any pipeline trigger except for pipelines associated with merge request events.
  5. manual_cleanup_only Job:
    • except: - schedules: This job will run for any pipeline trigger except when the pipeline is triggered by a predefined schedule.
  6. build_except_specific_branches Job:
    • except: - develop - release/v1.0: This job will be skipped if the pipeline is for the develop branch OR for the release/v1.0 branch. For all other branches/events, it will run.
  7. always_run_job_default Job:
    • This job has no except (or only or rules) clause. By default, such a job is included in all pipelines (unless a global workflow:rules configuration prevents the pipeline itself from running).
  8. advanced_except_example Job:
    • This demonstrates the refs keyword within except.
    • refs: - /^legacy-.*$/ - archive: The job will be skipped if the ref is a branch starting with “legacy-” (regex match) OR if it’s the archive branch.
    • Complexity Note: As with only, when you start combining conditions or using regex with except, the rules: syntax often provides a clearer and more maintainable way to express the logic.

Key Concepts and Behavior:

  • Blacklist: except defines when a job should not run. If any condition in except is met, the job is skipped.
  • Multiple Conditions: If you list multiple items in the except array (e.g., except: [main, develop]), the job is skipped if the pipeline event matches any of those items (logical OR).
  • Precedence: Similar to only, job-level except is evaluated after global workflow:rules.
  • Counterpart only: only is a whitelist. You can use only and except together in the same job definition, but this is strongly discouraged as it can lead to very complex and hard-to-understand logic. rules: handles these scenarios much better.
  • Special Keywords: The same special keywords available for only (like tags, merge_requests, schedules, branches, web, api, etc.) can also be used with except.

Recommendation: Use rules: Instead

It’s worth reiterating: GitLab now strongly recommends using rules: for controlling job execution due to its superior clarity and power.

Here’s a quick example of how build_on_feature_branches might look with rules:

YAML

build_on_feature_branches_with_rules:
  stage: build
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: never # Don't run if it's the main branch
    - when: on_success # Run in all other cases (e.g., feature branches)
  script:
    - echo "Building on a feature branch (using rules)"
    - echo "Current branch: $CI_COMMIT_REF_NAME"
Code language: PHP (php)

rules: allow for more complex if conditions using CI/CD variables, support when: manual, when: delayed, allow_failure, exists (for checking file existence), and generally make your pipeline’s conditional logic much more explicit and maintainable.

While understanding except is useful, especially if you encounter it in older GitLab CI configurations, aim to use rules: for any new CI/CD pipeline development or when refactoring existing ones.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x