Chapter 10Lesson 02~250 minutes

GitLab CI/CD Foundations: .gitlab-ci.yml, Pipelines, Jobs, Stages, and Execution Model: Guided Hands-On Workflow and Core Operations

Validate and run a tiny Free-compatible pipeline, prove its source and commit identity, inspect the assigned runner and logs, preserve a small artifact, and observe CI_JOB_TOKEN safely without revealing it.

Disposable labCI Lintglab ciJob logsRunner metadataArtifacts

Learning objectives

  • Create a disposable branch/project path and inspect runner availability before adding CI configuration.
  • Validate a minimal .gitlab-ci.yml in Pipeline Editor/CI Lint before committing it.
  • Run one harmless job and correlate pipeline source, ref, SHA, job ID, runner ID, and local Git state.
  • Add a tiny artifact and verify it as hosted evidence without confusing it with cache or workspace state.
  • Prove CI_JOB_TOKEN presence without printing its value and avoid creating any external side effect.
Availability baseline (verified 2026-08-21). Core GitLab CI/CD, .gitlab-ci.yml, Pipeline Editor/CI Lint, jobs, stages, ordinary branch and merge-request pipelines, predefined variables, artifacts, runner scheduling, and CI_JOB_TOKEN are available on GitLab Free across GitLab.com, Self-Managed, and Dedicated. GitLab-hosted runners are provided for GitLab.com and GitLab Dedicated; Self-Managed installations provide and operate their own runner capacity. Hosted compute quotas, credits, machine types, and billing can change, so this chapter never hard-codes them and provides a validation-only path whenever a runner is unavailable. Current glab uses the glab ci command family; older pipe/pipeline aliases are deprecated.

1. Disposable scenario and no-runner fallback

Use a disposable project such as GROUP/ch10-ci-foundations or a dedicated lab branch in a disposable project. The mandatory configuration prints only synthetic execution metadata and writes a tiny text artifact. It does not deploy, call cloud services, mutate registries, or create credentials.

  • Minimum role: enough project access to push a lab branch and create a pipeline. Maintainer/Owner is not required for the basic branch lab.
  • Offering/tier: GitLab Free on GitLab.com, Self-Managed, or Dedicated.
  • Runner path: if an eligible runner exists, run the job. If not, complete CI Lint/pipeline simulation and compare against the included expected-state fixture. Do not register a production runner merely to finish this lesson.

2. Preflight: observe before changing

Start by recording the repository and CI state. The commands below reveal metadata, not credentials.

REPO="GROUP/ch10-ci-foundations"
LAB_BRANCH="ch10/minimal-pipeline"
mkdir -p ch10-evidence

glab auth status
git status --short --branch
git remote -v
git rev-parse HEAD | tee ch10-evidence/local-head-before.txt

glab ci list -R "$REPO" --output json --per-page 10   --jq '.[] | {id,status,source,ref,sha}'   | tee ch10-evidence/pipelines-before.json

glab runner list -R "$REPO" --output json   | tee ch10-evidence/runners-before.json
Runner listing is read-only. Do not copy or expose runner authentication/registration tokens. A runner can be visible yet still be ineligible for a particular job because of tags, protection, scope, pause/offline state, or policy.

3. Write the first one-job configuration

The first configuration deliberately uses only ubiquitous shell behavior. It prints safe predefined metadata, verifies that the checked-out commit equals CI_COMMIT_SHA, verifies that the job token exists without printing it, and writes a tiny evidence file.

stages:
  - inspect

identity_probe:
  stage: inspect
  script:
    - printf 'pipeline_source=%s\n' "$CI_PIPELINE_SOURCE"
    - printf 'pipeline_id=%s\n' "$CI_PIPELINE_ID"
    - printf 'job_id=%s\n' "$CI_JOB_ID"
    - printf 'job_stage=%s\n' "$CI_JOB_STAGE"
    - printf 'commit_sha=%s\n' "$CI_COMMIT_SHA"
    - printf 'ref_name=%s\n' "$CI_COMMIT_REF_NAME"
    - printf 'runner_id=%s\n' "${CI_RUNNER_ID:-unknown}"
    - printf 'runner_description=%s\n' "${CI_RUNNER_DESCRIPTION:-unknown}"
    - test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
    - test -n "${CI_JOB_TOKEN:-}"
    - printf 'token_check=present-not-printed\n'
    - printf 'sha=%s\nsource=%s\n' "$CI_COMMIT_SHA" "$CI_PIPELINE_SOURCE" > ch10-evidence.txt
  artifacts:
    paths:
      - ch10-evidence.txt
    expire_in: 1 day

4. Validate before commit

Use Build → Pipeline editor → Validate or CI Lint before changing the repository. Syntax validation catches YAML/schema errors. Pipeline simulation goes further by trying to create the graph in a project/ref context and can catch logical problems that plain YAML parsers cannot.

If your current glab supports it, glab ci lint is another documented path. The CI Lint REST endpoint is also available, but a beginner should not create a new token only for this lab when the UI/CLI already provides safe validation.

Check What it proves What it does not prove
Generic YAML parser The document is syntactically YAML. That GitLab accepts keys/stages/rules.
CI Lint syntax GitLab accepts the CI configuration structure. That a particular source/ref will create the expected graph unless simulated.
Pipeline simulation GitLab can construct a graph in the chosen context. That a runner exists or job commands will succeed.
Successful job A runner executed the commands for one pipeline/SHA. That future commits, runners, or external systems behave identically.

5. Commit on a disposable branch and observe pipeline creation

git switch -c "$LAB_BRANCH"
# Save the YAML above as .gitlab-ci.yml, then:
git add -- .gitlab-ci.yml
git diff --cached --check
git diff --cached
git commit -m "ch10: add minimal observable pipeline"
LAB_SHA="$(git rev-parse HEAD)"
printf '%s\n' "$LAB_SHA" | tee ch10-evidence/lab-sha.txt
git push -u origin "$LAB_BRANCH"

# A push pipeline should normally have source=push unless workflow/rules say otherwise.
glab ci list -R "$REPO" --ref "$LAB_BRANCH" --output json --per-page 5   --jq '.[] | {id,status,source,ref,sha,web_url}' 

6. Correlate GitLab and runner evidence

Once the job is created, inspect it in Build → Pipelines and Build → Jobs. Confirm the pipeline SHA equals the recorded LAB_SHA. In the job page, record the runner description/ID and executor-related information GitLab exposes. Then compare the job log to your prediction.

# Current glab command family.
glab ci status -R "$REPO" --branch "$LAB_BRANCH" --compact
glab ci get -R "$REPO" --branch "$LAB_BRANCH" --output json --with-job-details   --jq '{id,status,source,ref,sha,jobs}'

# Optional live trace after choosing a job interactively:
glab ci trace -R "$REPO" --branch "$LAB_BRANCH"
Expected log pattern: source/ref/SHA IDs are printed, the checkout equality test is silent on success, and the token line says only present-not-printed. If a token value appears anywhere, stop the lab, treat it as exposed, and follow the credential-revocation guidance from Chapter 02.

7. Verify the artifact as a separate hosted object

After a successful job, GitLab stores ch10-evidence.txt as a job artifact because the job explicitly requested it. The artifact is not the runner workspace: the workspace can disappear while the uploaded artifact remains according to retention policy. Download or browse it in the job UI and verify it contains only the expected SHA/source lines.

# Expected artifact content (values differ per run):
sha=0123456789abcdef0123456789abcdef01234567
source=push

8. Determine hosted versus self-managed without guessing

On GitLab.com, GitLab-hosted runners are available by default for projects subject to current compute policy. A project/group can also have self-managed runners. On Self-Managed GitLab, the organization supplies runner infrastructure. On Dedicated, GitLab-hosted runner capability is available but tenant configuration matters.

Use the job’s runner identity plus Settings → CI/CD → Runners or glab runner list. Do not infer “GitLab-hosted” just because the runner has a generic name, and do not inspect config.toml on infrastructure you do not administer.

9. If no runner is available, finish without purchasing compute

A validated pipeline graph is still useful evidence. Preserve the CI Lint result, the predicted pipeline source/SHA, and this expected job-state fixture:

{
  "pipeline": {"source": "push", "ref": "ch10/minimal-pipeline", "sha": "<LAB_SHA>"},
  "job": {"name": "identity_probe", "stage": "inspect", "expected_status": "pending_without_runner_or_success_with_runner"},
  "secret_output": "none",
  "external_side_effects": "none"
}
Do not enable a paid runner plan, register an employer runner, or weaken runner policy to make the exercise green. A course lab must adapt to the available trust boundary, not redefine it.

10. Challenge: choose the correct surface

The job is present but pending. Which surface do you inspect first: Pipeline Editor, repository diff, or runner eligibility? Choose runner eligibility because pipeline creation already succeeded. Now suppose no pipeline exists after the push: move your investigation back to configuration/ref/source and workflow logic. The resource state tells you which subsystem owns the next diagnostic question.

11. Cleanup and verification

Keep the pipeline evidence long enough to complete the lesson, then remove only the synthetic CI change. If the whole project is disposable, you may archive/delete it only after confirming it contains nothing valuable. Otherwise revert .gitlab-ci.yml on the lab branch or delete that branch after preserving the commit SHA and pipeline URL.

git fetch origin --prune
git show --stat "$LAB_SHA"
# Verify the branch points to the expected lab commit before deleting it.
git ls-remote --heads origin "$LAB_BRANCH"
# Then remove the disposable branch through GitLab UI or:
git push origin --delete "$LAB_BRANCH"
git fetch origin --prune

Knowledge check

Why lint before pushing the first .gitlab-ci.yml?

The pipeline SHA differs from your current local HEAD after you made another local commit. Is the pipeline wrong?

How did the lab prove CI_JOB_TOKEN existed without leaking it?

What proves which runner executed the job?

Why is ch10-evidence.txt an artifact rather than just a file?

Summary

You validated configuration before mutation, ran or simulated one deterministic job, correlated source/ref/SHA with local Git, inspected runner assignment, verified temporary job identity without printing it, and separated uploaded artifact evidence from runner workspace state. The workflow remains useful even when hosted compute is unavailable.

Official references

Next lesson

Choose the execution design deliberately

Lesson 3 compares simple stages with later DAG optimization, GitLab-hosted with self-managed runners, branch with merge-request pipelines, and integrated platform coverage with the separate GitLab CI/CD course boundary.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.