GitLab CI/CD Associate study materials
GitLab CI/CD Associate study resources

GitLab CI/CD Associate common mistakes

GitLab CI/CD Associate · Understand the traps and how to avoid them.

Mistake 1

Writing job names that collide with reserved keywords such as image, services, stages, variables, cache, before_script, or after_script, so the pipeline fails to parse or the keyword is silently overridden.

Candidates treat top-level keys and job names as interchangeable and copy snippets where a job is called test next to a keyword, or they define variables: inside a job without realizing some names are reserved. GitLab reserves a fixed keyword list and a job may not share a name with a global keyword.

What to do instead

Memorize the reserved-keyword list and rename jobs with a suffix (e.g. test-unit, build-image). Keep global keywords only at the top level and job-level overrides nested under the job. Validate with the CI Lint tool before pushing.

Mistake 2

Assuming jobs in the same stage run sequentially and that a later job can read files written by an earlier job in the same stage.

Candidates carry over intuition from single-machine build scripts. In GitLab, jobs in the same stage run in parallel on separate runners with separate working directories, so there is no shared filesystem between them.

What to do instead

Model the pipeline as stages of parallel jobs with no shared state. To pass files between jobs, declare them under artifacts:paths in the producing job and rely on dependencies or needs in the consuming job; to run things in order, put them in different stages or chain with needs.

Mistake 3

Using `only`/`except` syntax when the task calls for `rules`, or mixing both in one job, and expecting `when: manual` or `allow_failure` semantics to behave the same.

Older tutorials and legacy examples still use only/except, and the two systems look similar. GitLab does not allow combining only/except with rules in the same job, and rules evaluates top to bottom with first match winning, which changes which jobs are created.

What to do instead

Prefer rules with explicit if, changes, and exists clauses, remembering that the first matching rule decides and later rules are ignored. Never mix the two keywords in one job. Test rule outcomes with the CI Lint 'simulate pipeline' feature.

Mistake 4

Expecting a variable defined in `variables:` at the top level to be available inside a `script` that is executed by a shell without exporting it, or expecting a variable set in one job to be visible in another job.

Candidates conflate YAML-level variables with shell environment state. Variables defined in one job's script line do not persist to subsequent lines in some shells unless exported, and job-level variables never cross job boundaries because each job is a fresh process.

What to do instead

Define shared values under top-level variables: or workflow:rules:variables, use export VAR=value for values needed later in the same script, and pass data between jobs only through artifacts or dotenv artifact reports. Verify precedence: pipeline variables override job variables, which override global variables.

Mistake 5

Marking a variable as protected but running the pipeline from a feature branch or merge request, then wondering why the variable is empty.

Protected variables are only injected into pipelines running on protected branches or protected tags. Merge request pipelines from unmerged source branches, and pipelines on unprotected branches, do not receive them, so the job sees an empty string rather than an error.

What to do instead

Check the branch/tag protection settings against where the pipeline runs. For MR pipelines that genuinely need a secret, use a protected environment with environment-scoped variables or a separate secret store, and add a guard in the script that fails fast when the variable is empty.

Mistake 6

Setting `cache:key` without a stable key or with a key that includes `$CI_COMMIT_SHA`, causing a near-zero cache hit rate while still paying upload/download time.

Candidates assume cache is a reliable artifact store. Cache keys are matched exactly (with fallback rules), and including the commit SHA makes every pipeline a new key. Cache is also runner-dependent: a cache written on one runner may not be readable on another unless distributed cache is configured.

What to do instead

Use a key based on lockfile content (e.g. key: files: [package-lock.json]) or a branch-scoped key like $CI_COMMIT_REF_SLUG, and use cache:policy: pull for read-only jobs. Use artifacts, not cache, for anything the pipeline must depend on.

Mistake 7

Confusing `dependencies` with `needs`, or omitting `needs` and expecting downstream jobs to start before the whole previous stage finishes.

dependencies only controls which artifacts a job downloads; it does not change scheduling. Without needs, a job waits for all jobs in earlier stages. Candidates also assume needs implies artifact download in every case, when artifact passing with needs follows its own rules.

What to do instead

Use needs to build a DAG and start jobs early; add needs: [{job: X, artifacts: true}] when artifacts are required. Use dependencies: [] to skip downloads. Remember needs can reference jobs in the same stage, which is the main way to get parallel-stage execution.

Mistake 8

Assuming a runner tagged `docker` will pick up a job that specifies `tags: [shell]`, or leaving `tags` off and expecting a specific runner to be chosen.

Runner selection requires that the job's tags be a subset of the runner's tags, and untagged jobs only run on runners configured to accept untagged jobs. Candidates also forget that tags is separate from the image keyword, which selects an executor image, not a runner.

What to do instead

Decide a tagging convention (e.g. by capability or environment) and apply it consistently to jobs and runners. Check the runner's 'Run untagged jobs' setting. Confirm with the job log header, which names the runner and executor used.

Mistake 9

Debugging a failing job by only reading the last lines of the log, without checking whether the failure is a script exit code, a missing artifact, or a runner-level error such as image pull failure or timeout.

GitLab reports several distinct failure classes with similar red output. A non-zero exit from a script command, an artifact download 404, an executor image pull error, and a job timeout all look like 'job failed' to an untrained eye, and each has a different fix.

What to do instead

Read the job log from the top: the executor and image lines, then the 'Getting source from Git repository' section, then the script section. Distinguish exit code failures (script bug) from 'no matching runner' (tags), 'pull access denied' (registry auth), and 'job exceeded timeout' (raise timeout or split the job). Reproduce locally with the same image where possible.

Mistake 10

Writing merge request pipeline rules that also trigger branch pipelines, producing duplicate pipelines for the same commit, or using `$CI_MERGE_REQUEST_ID` in a branch pipeline where it is undefined.

$CI_PIPELINE_SOURCE and the $CI_MERGE_REQUEST_* variables only exist in MR pipelines. Rules that check only $CI_COMMIT_BRANCH will fire on both branch and MR pipelines, and rules that check $CI_MERGE_REQUEST_ID in a branch context evaluate to empty and can match unexpectedly.

What to do instead

Use workflow:rules to prevent duplicate pipelines, e.g. run branch pipelines only when no MR is open and MR pipelines when $CI_PIPELINE_SOURCE == 'merge_request_event'. In job rules, gate MR-only jobs on $CI_PIPELINE_SOURCE == 'merge_request_event' and branch-only jobs on $CI_COMMIT_BRANCH.