Matrix & Parallel Jobs

Learn how to use GitLab CI/CD parallel and matrix jobs to run multi-variant builds efficiently, create dynamic job dependencies with matrix expressions, and avoid massive YAML duplication.

Parallel Matrix GitLab CI/CD
Why Parallel and Matrix Jobs?

As applications grow, so does the testing matrix. You may need to test against multiple operating systems, language versions, database versions, or architecture targets. Writing a separate job for every combination quickly becomes unmanageable — a matrix of 3 operating systems × 4 language versions × 2 architectures means 24 nearly identical job definitions, each differing only in a few variables. This duplication is error-prone to maintain and makes the .gitlab-ci.yml file bloated and hard to read.

GitLab CI/CD solves this with two related features: parallel and parallel:matrix. The parallel keyword simply runs the same job multiple times, splitting work by index — useful for sharding a large test suite. The parallel:matrix keyword is more powerful: it generates multiple jobs from a single definition, each with a different combination of variable values, allowing you to test across a full matrix of configurations without repeating yourself [citation:7][citation:9].

Matrix jobs also introduce a challenge: dependencies. If your build stage creates 24 image variants, your deploy stage needs to consume the matching artifact from each build, not all 24 at once. GitLab solves this with matrix expressions ($[[ matrix.IDENTIFIER ]]), which create 1:1 mappings between matrix jobs across stages, ensuring each downstream job depends only on its corresponding upstream job [citation:2][citation:4].

Core Principle: Use parallel:matrix when you need the same job logic with different variable combinations. Use matrix expressions in needs to wire up matching dependencies between stages.

parallel

Runs the same job N times. Each instance gets $CI_NODE_INDEX and $CI_NODE_TOTAL for sharding. No variable variation.

parallel:matrix

Generates jobs from a matrix of variable values. Each combination becomes a separate job with its own variable values injected.

Matrix Expressions

$[[ matrix.IDENTIFIER ]] creates 1:1 dependencies between matrix jobs in different stages. GitLab 18.6+ (Beta).

Job Limits

Matrix permutations cannot exceed 200 per job. Total jobs in active pipelines are also limited at the instance level.
Using the parallel Keyword

The parallel keyword is the simpler of the two features. When you set parallel: 5 on a job, GitLab creates five identical instances of that job, named job_name 1/5 through job_name 5/5. Each instance runs in parallel, and GitLab injects two environment variables: $CI_NODE_INDEX (the instance number, starting from 1) and $CI_NODE_TOTAL (the total number of instances) [citation:9].

This is primarily useful for sharding — splitting a large test suite across multiple runners. For example, if you have 1000 tests and 5 parallel jobs, each job can run 200 tests, reducing wall-clock time by a factor of five (assuming enough runners are available). The test framework must support sharding by index, which most modern test runners do.

Note that parallel alone does not vary configuration between instances. All five jobs use the same image, same script, and same variables (except the built-in index variables). If you need different configurations, you need parallel:matrix.

# Basic parallel: run 5 identical jobs test: stage: test parallel: 5 script: - echo "Running test shard $CI_NODE_INDEX of $CI_NODE_TOTAL" - npm run test:shard-$CI_NODE_INDEX # Sharding a test suite test_sharded: stage: test parallel: 4 script: - echo "Shard $CI_NODE_INDEX/$CI_NODE_TOTAL" # Test framework splits tests based on node index - pytest --shard-id=$CI_NODE_INDEX --num-shards=$CI_NODE_TOTAL # Parallel with artifacts (each instance uploads) test_parallel: stage: test parallel: 3 script: - npm test -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL artifacts: when: always paths: - test-results-$CI_NODE_INDEX/ expire_in: 1 day # Important: parallel creates identical jobs. # For different configurations, use parallel:matrix.
Parallel Job Limits:
  • Jobs created by parallel count toward your instance's job limit
  • If you don't have enough runners, excess jobs are queued as pending
  • Too many parallel jobs can cause job_activity_limit_exceeded errors [citation:9]
  • Start with a modest number (3-5) and scale up based on runner capacity
Using parallel:matrix

parallel:matrix is where the real power lies. Instead of running identical jobs, it generates a job for each combination of variable values you specify. This is the GitLab equivalent of GitHub Actions' matrix strategy, and it's essential for testing across multiple environments without duplicating YAML [citation:7].

The matrix is defined as a list of dictionaries. Each dictionary can contain one or more variables, each with a list of values. GitLab computes the Cartesian product of all values across all dictionaries in the list. For example, if you define OS: [ubuntu, alpine] and NODE: [16, 18, 20] in a single dictionary, you get 6 jobs (2 × 3). If you define them in separate dictionaries, you also get 6 jobs — the matrix always expands all combinations [citation:4].

Each generated job receives the variable values as environment variables, so your script can reference $OS and $NODE directly. This makes the single job definition reusable across all combinations, which is the core benefit: write once, run everywhere.

# Basic matrix: 2 OS × 3 Node versions = 6 jobs test: stage: test parallel: matrix: - OS: [ubuntu, alpine] NODE: [16, 18, 20] script: - echo "Testing on $OS with Node $NODE" - npm test # Matrix with single variable (3 jobs) build: stage: build parallel: matrix: - ARCH: [amd64, arm64, s390x] script: - echo "Building for $ARCH" - ./build.sh --arch=$ARCH # Matrix with multiple dictionaries (Cartesian product) deploy: stage: deploy parallel: matrix: - ENV: [staging, production] - REGION: [us-east, eu-west] script: - echo "Deploying to $ENV in $REGION" # Creates 4 jobs: staging/us-east, staging/eu-west, # production/us-east, production/eu-west # Using matrix variables in image names test_versions: stage: test image: node:$NODE_VERSION parallel: matrix: - NODE_VERSION: ["16", "18", "20"] script: - node --version - npm test # Matrix with YAML anchors for reuse .build_matrix: &build_matrix parallel: matrix: - OS: ["ubuntu", "alpine"] ARCH: ["amd64", "arm64"] VARIANT: ["slim", "full"] compile: stage: build <<: *build_matrix script: - echo "Compiling for $OS-$ARCH-$VARIANT" # Creates 8 jobs (2 × 2 × 2) # Matrix limits: # - Max 200 permutations per job # - Variable names: letters, numbers, underscores only # - Values must be strings or arrays of strings
Matrix Best Practices: