cswork / libraryOpen practice workspace

Articles

GitLab CI Interview Questions: Rules, DAGs, Artifacts, and Runner Boundaries

Prepare for GitLab CI interviews with workflow and job rules, needs DAGs, artifact contracts, runner protection, and a traceable YAML exercise.

A GitLab CI pipeline can be green for the wrong reason: a job never appeared, a consumer started without its artifact, or a deployment ran on a runner that should never have seen the credentials. In a GitLab CI interview, naming rules, needs, and runner tags is not enough. You need to predict the pipeline graph for a particular event and show where data and trust cross job boundaries.

This guide uses one small .gitlab-ci.yml to answer interview questions about duplicate pipelines, DAG scheduling, artifact handoff, and protected deployment.

Evidence boundary: GitLab's current documentation supports the configuration semantics described here. The YAML is an illustrative interview exercise; adapt images, scripts, branch policy, GitLab version, runner configuration, and protected-environment permissions before using it in production. This is not a claim about a named employer's interview.

Start with the event: why did two pipelines appear?

Suppose a developer pushes a commit to a branch with an open merge request. GitLab can create a branch pipeline and a merge-request pipeline unless the configuration controls which pipeline types are admitted. A common interview bug is to place a permissive rule on individual jobs and assume it prevents duplicate pipelines. Job rules decide whether a job is included after a pipeline type exists; workflow: rules decides whether the pipeline itself is created. GitLab's job-rules documentation explicitly warns about configurations that produce duplicate pipelines.

Before rewriting YAML, identify the event: CI_PIPELINE_SOURCE, branch or tag, open merge-request state, and whether the project runs merged-results or fork pipelines. “Why is this job missing?” and “Why are there two pipelines?” are different questions. In the example below, the workflow admits merge-request pipelines, suppresses branch push pipelines when the branch has an open merge request, then admits other branch pipelines. Tags and schedules are excluded by this example unless you add explicit rules.

stages: [check, build, test, deploy]

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"'
      when: never
    - if: '$CI_COMMIT_BRANCH'

default:
  image: alpine:3.20

lint:
  stage: check
  needs: []
  script: ./ci/lint.sh

build:
  stage: build
  script: ./ci/build.sh
  artifacts:
    paths: [dist/]
    expire_in: 7 days

unit_test:
  stage: test
  needs:
    - job: build
      artifacts: true
  script: ./ci/unit-test.sh dist/

deploy_production:
  stage: deploy
  needs:
    - job: build
      artifacts: true
    - job: unit_test
      artifacts: false
  tags: [protected-deploy]
  environment:
    name: production
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push"'
      when: manual
    - when: never
  script: ./ci/deploy.sh dist/

This is intentionally a traceable graph, not a copy-paste universal pipeline. lint can run as soon as the pipeline starts because needs: [] removes stage waiting. unit_test needs build and downloads its artifacts. deploy_production waits for both build and tests, downloads build output, and does not download artifacts from unit_test. The manual deploy job appears only on a default-branch push. If your repository does not have ./ci/*.sh, the jobs will fail; these are placeholders for the exercise.

Trace three events through workflow and job rules

For a feature-branch push with no open merge request, the first two workflow rules do not match, then the branch rule admits a branch pipeline. The check, build, and test jobs appear. Deployment does not, because its default-branch rule fails. For a push to a feature branch with an open merge request, the second workflow rule blocks the push pipeline. A separate merge-request event matches the first rule and gets a single intended MR pipeline. For a default-branch push, the branch rule admits the pipeline; the manual deployment job is present if the deploy rule matches.

Do not reduce this to “there is one pipeline per commit” in every setup. Merge trains, merged-results pipelines, scheduled events, child pipelines, and project-level settings can change the event set. State the exercise's scope and inspect actual CI_PIPELINE_SOURCE values when debugging. The workflow example also excludes tag pipelines; if releases use tags, add an explicit tag rule and test it. A final catch-all job rule can accidentally make a job appear in both push and MR pipelines, so do not add one casually.

The first diagnostic artifact in an interview can be a three-row truth table: event, pipeline admitted?, deployment job present? It often locates the bug faster than rereading the whole YAML. Use GitLab's CI Lint and pipeline simulation facilities for the repository's actual version and variables, then compare with a real run. A local YAML parser can confirm syntax but cannot reproduce GitLab's event and permission semantics.

Explain the DAG, not just the stage list

Stages provide an ordering default. needs creates explicit job-level dependencies, allowing a job to start when its required jobs finish rather than waiting for every job in an earlier stage. In the example, lint has an empty needs list and can begin immediately, while unit_test waits for build. deploy_production waits for both the artifact producer and the test gate. That structure makes the critical path visible. It does not make an unsafe test optional merely because faster scheduling sounds attractive.

Now introduce the interview trap: suppose build is omitted on documentation-only changes by its own rules, but unit_test still declares needs: [build]. GitLab can fail pipeline creation because the needed job is absent. needs:optional is appropriate only when the consumer genuinely works without that producer. Here unit_test calls ./ci/unit-test.sh dist/; it requires the package, so making build optional would hide a broken contract. Align the producer and consumer rules, or split the tests into one path that does not need dist/ and another that does.

Another trap is a needs edge that names a job from a conditional child pipeline or a parallel matrix incorrectly. Explain what artifacts each consumer expects and which event includes each producer. If a test can compile directly from source, remove the dependency and say so. If it must test the exact deployable output, retain the dependency and make the artifact identity explicit. Optimizing a graph is legitimate only after preserving the test guarantee.

Artifacts are a data contract between jobs

The build job packages dist/ as a GitLab job artifact. unit_test and deploy_production list build with artifacts: true, so the expected output is available to their scripts. With needs, artifact download behavior differs from the broad stage-default assumption; explicitly state which upstream output each job needs. artifacts: false on the test edge means deployment waits for successful tests but does not download test artifacts. That is a dependency and a data-transfer decision in one stanza.

Artifacts are not caches. A cache is an optimization for reusable dependencies and may be shared or replaced according to cache keys and runner/storage configuration. A build artifact is the output of a particular successful job that downstream jobs should consume. For release safety, consider an immutable package or image digest and promote that exact tested object. Rebuilding after tests can produce a different binary even when the source commit is unchanged, for example when dependencies or toolchain images have moved.

The example expires artifacts after seven days. If manual production approval can happen later, that expiry may make deployment impossible; coordinate retention with the approval window or use a durable package registry. Also consider that artifact paths can contain secrets by accident. Review what is uploaded, who can download it, and whether build logs expose values. Do not use artifacts as a substitute for short-lived, scoped deployment credentials.

If the interviewer says “deploy downloads the latest successful build,” ask what “latest” means across branches and retries. A safe design binds deploy to the artifact from its own pipeline or a verified immutable release candidate. The job graph alone cannot guarantee provenance if the deployment script fetches an unpinned object from elsewhere.

Runner tags are scheduling; protection is a trust boundary

The protected-deploy tag requests a matching runner. A tag by itself is not authorization. A deployment boundary also needs the runner configured appropriately, a protected branch or tag policy, protected variables or federated identity with minimal scope, and a protected production environment with the intended approvers and permissions. GitLab documents how runner tags and protected runners affect eligible jobs; verify the actual project settings instead of inferring them from YAML alone.

Ask where untrusted code can execute. A merge-request pipeline from a fork may contain code controlled by someone without deployment authority. It should not gain production secrets because its job name contains “deploy” or because a shared runner happens to have a matching tag. The example gates deployment to a default-branch push and a manual action, but you must still inspect branch protection, environment permissions, runner protection, token exposure, and whether a pipeline definition itself can be changed by an untrusted actor.

Separate job isolation from secret scope. A container executor does not automatically make a shared runner safe for hostile code if host mounts, privileged mode, persistent workspaces, or broad cloud credentials are present. For high-trust deployment, dedicate a protected runner or appropriate executor and use short-lived credentials tied to the exact repository, ref, environment, and action where possible. The interview answer should identify the trust boundary, not merely say “use CI variables.”

Debug a failure in a reproducible order

When a pipeline is missing, inspect workflow rules and the event payload. When a job is missing, inspect its rules and whether inherited configuration changed it. When pipeline creation fails, inspect needs references to conditionally absent jobs. When a job is stuck, inspect runner availability, tags, protected status, and capacity. When a downstream job cannot see dist/, inspect the producer's artifact paths, upload result, expiry, and the consumer's needs:artifacts specification. When a deploy fails only on MR versus default branch, inspect permission and variable scope before retrying with broader credentials.

For each failure, capture the pipeline ID, source, commit SHA, job IDs, runner ID or class, artifact metadata, and relevant log line. Compare one failing run with one known-good run. A log message such as “file not found” could be a wrong path, a skipped producer, an expired artifact, or a fetch from another pipeline. The hypothesis determines the next check; changing YAML blindly can create a second failure while hiding the first.

In the interview, narrate one prediction before opening the result: “For this MR event I expect workflow to admit the MR pipeline, block the branch push duplicate, include build and unit_test, and omit deploy.” Then inspect the UI or CI Lint against that expectation. This turns configuration trivia into a falsifiable debugging method.

A concise interview answer

“I start with the pipeline event. workflow:rules controls whether a branch, MR, tag, or scheduled pipeline exists; job rules control inclusion inside it. Then I trace needs as a DAG and state which artifact each consumer needs. If a conditional producer is absent, I align rules rather than applying optional:true to a consumer that cannot run without its output. I treat runner tags as scheduling, not security. Production deployment needs protected refs, environment permissions, appropriate runner isolation, and scoped credentials. I verify each claim with an event-specific pipeline trace and artifact readback.”

That answer is stronger than memorizing keywords because it predicts failures. Rehearse by removing build from one event, expiring its artifact, then changing the MR source. Explain what breaks and what evidence would prove each diagnosis. Start with Design a Dependency-Aware CI/CD Pipeline and draw the exact job graph.

Sources and Further Reading

Research checked September 23, 2026. Validate the YAML and permission model against your project's GitLab version and settings.