Continuous Integration and Delivery Foundations, GitLab Pipeline Architecture, and Delivery Flow: Guided Hands-On Workflow and Core Operations
Turn the Chapter 01 state model into a controlled experiment. You will validate a minimal configuration, create a push pipeline, create a manual pipeline for the same disposable branch, compare pipeline source and SHA evidence, inspect job and runner state without exposing credentials, preserve one synthetic artifact, and clean up only the resources created by the lab.
Learning objectives
- Create and validate a minimal disposable GitLab CI/CD configuration before using runner compute.
-
Trigger a branch push pipeline and a manual web pipeline, then
compare
CI_PIPELINE_SOURCE, ref, SHA, pipeline ID, and job ID. - Inspect runner assignment and executor-facing evidence without logging tokens or broad variable dumps.
- Persist a small synthetic evidence file as an artifact and distinguish it from incidental runner workspace state.
- Verify cleanup using exact branch/project identity and complete the lab without requiring paid or production infrastructure.
1. Scenario: one project, one lab branch, two pipeline sources
The purpose of this lab is to change exactly one variable at a time.
The Git commit will stay fixed while the pipeline source changes.
First, a push to the lab branch should create a
push pipeline. After that pipeline is observed, use
GitLab's Build → Pipelines → New pipeline interface
to create a second pipeline for the same branch. That pipeline
should report source web. If the branch has not moved,
both pipelines should refer to the same commit SHA but have
different pipeline IDs and sources.
That comparison demonstrates why “what commit?” and “why did this
pipeline exist?” are separate questions. Later chapters will use
that distinction in workflow:rules and job
rules. Here we only observe it.
One disposable branch and one known commit SHA.
Pipeline source: push versus web.
Two pipeline IDs; same SHA if the branch did not move.
None. No deployment, registry, cloud, or API mutation.
2. Preflight: record repository state before changing it
Start from a local clone of the disposable project. Record enough Git state to prove exactly what you are about to push. Do not create a new personal access token merely for this lesson; normal Git authentication already configured for the disposable project is sufficient.
LAB_BRANCH="glci/ch01-observe"
git status --short --branch
git remote -v
git rev-parse HEAD
git log -1 --oneline --decorate
In GitLab, also inspect Settings → CI/CD → Runners if your role exposes it. The question is read-only: is there an eligible runner that is appropriate for disposable course code? Do not copy runner authentication tokens and do not modify runner protection, tags, or privileges just to make the lab green.
3. Write the smallest observable pipeline
Save the following file as .gitlab-ci.yml at the
project root. The job uses ordinary POSIX-style shell commands and
assumes the selected runner's execution environment provides Git and
a compatible shell. It deliberately declares no secret variables and
performs no authenticated API calls.
stages:
- observe
observe_identity:
stage: observe
script:
- mkdir -p evidence
- printf 'pipeline_source=%s\n' "$CI_PIPELINE_SOURCE"
- printf 'ref=%s\n' "$CI_COMMIT_REF_NAME"
- printf 'sha=%s\n' "$CI_COMMIT_SHA"
- printf 'pipeline_id=%s\n' "$CI_PIPELINE_ID"
- printf 'job_id=%s\n' "$CI_JOB_ID"
- printf 'runner_id=%s\n' "${CI_RUNNER_ID:-unknown}"
- printf 'runner_version=%s\n' "${CI_RUNNER_VERSION:-unknown}"
- test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
- printf 'source=%s\nsha=%s\npipeline_id=%s\njob_id=%s\n' "$CI_PIPELINE_SOURCE" "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_JOB_ID" > evidence/identity.txt
artifacts:
paths:
- evidence/identity.txt
expire_in: 1 day
Notice what is absent: no CI_JOB_TOKEN output, no
printenv, no cloud credential, no registry login, and
no mutable external container image selected by this lesson. GitLab
Runner's executor decides the execution environment. If your runner
does not provide the expected shell/Git behavior, treat that as an
explicit runner compatibility observation rather than weakening
security controls.
4. Validate before you commit
Open Build → Pipeline editor → Validate and validate the configuration. Current GitLab documentation describes CI Lint as a Free-tier capability across GitLab offerings and supports pipeline-creation simulation for more complicated logic. The pipeline editor also validates syntax automatically.
If you already use the GitLab CLI, the current command is
glab ci lint; the --dry-run mode can
simulate pipeline creation and --ref can select the
branch/tag context. CLI use is optional. Do not install tools or
create credentials solely because a copied tutorial expects them.
# Optional, only if glab is already authenticated to the disposable project.
glab ci lint
# After the lab branch exists remotely, a simulation can be scoped to it:
glab ci lint --dry-run --include-jobs --ref "$LAB_BRANCH"
| Observation | What it proves | What remains unproved |
|---|---|---|
| Generic YAML parses | The document is syntactically YAML. | That GitLab CI/CD accepts its semantics. |
| CI Lint valid | GitLab accepts the configuration in the lint context. | Runner availability and command behavior. |
Simulation creates observe_identity |
The simulated pipeline graph includes the expected job. | That a runner can execute it. |
| Live job succeeds | One concrete runner execution completed successfully. | External target health—there is no external target in this lab. |
5. Create the push pipeline from an exact commit
Create a dedicated branch, review the staged diff, commit the CI file, and record the commit SHA before pushing. The recorded SHA becomes the reference point for all later comparisons.
git switch -c "$LAB_BRANCH"
git add -- .gitlab-ci.yml
git diff --cached --check
git diff --cached
git commit -m "glci ch01: add observable pipeline"
LAB_SHA="$(git rev-parse HEAD)"
printf 'lab_sha=%s\n' "$LAB_SHA"
git push -u origin "$LAB_BRANCH"
Open Build → Pipelines. Locate the pipeline for the
recorded branch/SHA rather than selecting an ambiguous “latest”
pipeline. Confirm its source is push. Open the
pipeline, then open observe_identity. Record the
pipeline ID and job ID before retrying or changing anything.
6. Follow the evidence into the job
A running or completed job gives you execution-plane evidence.
Compare the safe log lines to the control-plane metadata shown in
the pipeline/job UI. The SHA in the job must match the pipeline SHA
and the recorded LAB_SHA. The checked-out
git rev-parse HEAD test should also match for this
simple branch pipeline.
| Evidence | Expected value | Why it matters |
|---|---|---|
| Pipeline source | push |
Proves why this first pipeline was created. |
| Pipeline SHA | LAB_SHA |
Ties GitLab state to immutable source. |
| Job SHA / checkout HEAD | Same SHA in this scenario | Proves which source the script actually sees. |
| Pipeline ID | A concrete integer | Correlation key for pipeline evidence. |
| Job ID | A concrete integer | Correlation key for trace/artifact evidence. |
| Runner ID/version | Assigned runner metadata | Identifies execution infrastructure when a runner exists. |
If the job is pending and has no runner assignment, preserve the pipeline/job IDs and stop at that boundary. The correct conclusion is “configuration and pipeline creation succeeded; execution has not started.” Do not infer a script failure from a script that never ran.
7. Verify the artifact as durable GitLab-managed evidence
When the job succeeds, GitLab Runner uploads
evidence/identity.txt because the job explicitly
declared it under artifacts:paths. Download or browse
the artifact from the job/pipeline interface and compare it with the
log and pipeline metadata. It should contain only four non-secret
fields.
source=push
sha=3f4c...actual-commit-sha...
pipeline_id=12345
job_id=67890
The file's existence on GitLab after the job is different from its existence in the runner workspace. The artifact has a producer job, pipeline/SHA context, and retention lifecycle. This distinction becomes essential when later jobs execute on different runners.
8. Trigger the same branch manually and compare source identity
Without changing the branch, open
Build → Pipelines → New pipeline, select
glci/ch01-observe, and create the pipeline. Do not add
arbitrary variables; this comparison is about pipeline source only.
Current GitLab pipeline-source semantics identify a pipeline created
from the web interface as source web.
Open the new observe_identity job and compare the
evidence packet with the push pipeline. If no commit was added
between the two runs, you should see:
- different pipeline IDs;
- different job IDs;
-
pushversuswebas pipeline source; - the same branch/ref;
- the same commit SHA;
- possibly a different runner ID if scheduling selected another runner.
9. No eligible runner? Complete the learning without weakening controls
If CI Lint and pipeline simulation succeed but a live job cannot be executed safely, preserve the lint result and use the fixture below to state your expected live evidence. Mark it as a simulation, not as observed runtime evidence.
{
"project": "disposable GitLab lab project",
"branch": "glci/ch01-observe",
"sha": "captured-lab-sha",
"push_pipeline": {
"source": "push",
"expected_job": "observe_identity"
},
"manual_pipeline": {
"source": "web",
"expected_job": "observe_identity"
},
"external_side_effects": "none",
"runtime_evidence": "not observed because no eligible disposable runner was available"
}
This is not equivalent to a live run, and the lesson should say so. It is still better than granting an unsafe runner more privilege or attaching course code to infrastructure that was never intended for it.
10. Map the lab to CI, delivery, and deployment
The lab is CI observation. It associates an exact source revision with pipeline/job execution evidence and one stored artifact. It is not continuous delivery because it does not create or promote a deployable application artifact. It is not deployment because it changes no environment. This negative boundary is useful: a pipeline can be valuable even when it never deploys.
In later chapters, a build job may produce an immutable package or image, a security job may generate reports, an environment job may require approval, and a deployment job may authenticate through OIDC. Those additional states extend this evidence chain; they do not replace it.
11. Challenge: choose the correct diagnostic surface
For each symptom, identify the first useful layer before reading the answer:
- No pipeline appears after push. Start with ref/source/configuration and pipeline-creation logic.
- Pipeline exists but job is pending. Start with job eligibility and runner matching/capacity.
- Job runs and exits 1. Start with the first failing command and execution environment.
- Job succeeds but artifact is absent. Inspect artifact path/upload configuration and runner artifact-upload evidence.
- Manual pipeline uses a new SHA. Verify whether the branch moved between the push and manual runs before assuming GitLab selected the wrong commit.
12. Cleanup: delete only the resource you can identify exactly
Preserve the two pipeline IDs, two job IDs, sources, SHA, runner metadata (if any), and artifact evidence long enough to finish the comparison. Then clean up only the disposable branch or project you created. Before deleting the branch, verify that the remote ref still points to the expected lab history.
git fetch origin --prune
git log -1 --oneline "$LAB_BRANCH"
git ls-remote --heads origin "$LAB_BRANCH"
# Only after verifying this is the disposable lab branch:
git push origin --delete "$LAB_BRANCH"
git switch -
git branch -D "$LAB_BRANCH"
git fetch origin --prune
Summary
You separated validation from execution, tied two pipelines to source/ref/SHA evidence, changed the pipeline source without intentionally changing the commit, inspected runner identity only after assignment, persisted a synthetic artifact instead of relying on workspace state, and kept the entire exercise free of production credentials and deployment side effects.
Knowledge check
Why validate with CI Lint before spending runner time?
It separates configuration/graph problems from runner and command-execution problems. A valid lint result is earlier evidence, not a guarantee of job success.
Two pipelines have the same SHA but sources
push and web. Is that
contradictory?
No. The source records why a pipeline was created; multiple pipeline sources can evaluate the same immutable commit.
A job is pending with no runner ID. Should you debug the first shell command?
No. The command has not executed. Inspect runner matching, availability, scope, tags, protection, and capacity first.
Why is evidence/identity.txt more durable than a
file left only in the runner workspace?
The configured artifact is explicitly uploaded to GitLab and has a retention lifecycle tied to the producing job. Incidental workspace state is not a cross-job or cross-run contract.
What should you do if the only available runner is privileged production infrastructure?
Use CI Lint/pipeline simulation and document the runtime limitation. Do not weaken the production trust boundary to complete a learning exercise.
Official references and version notes
- Get started with GitLab CI/CD — current first-principles overview of pipeline configuration, jobs, stages, and runners.
- CI/CD pipelines — pipeline sources, basic/stage/DAG pipeline forms, and current pipeline behavior.
- CI/CD YAML syntax reference — authoritative keyword semantics and compatibility notes.
- Predefined CI/CD variables — availability phases and runtime identity variables such as source, SHA, pipeline, job, and runner metadata.
- Runners — current runner scheduling and execution model.
- Validate GitLab CI/CD configuration — syntax/logic validation and current pipeline-simulation behavior.
- glab ci lint — current optional CLI validation and dry-run flags.
- Job artifacts — artifact upload, download, retention, and cross-job behavior.
- Specify when jobs run with rules — current pipeline-source values, including push and web contexts used by later rule design.
UI wording, GitLab CLI flags, runner offerings, and hosted-compute quotas can change. The steps and command family in this lesson were checked against primary GitLab documentation on 2026-09-11. The mandatory learning objective remains source/SHA/pipeline/job/runner evidence and can be completed with validation/simulation if safe runner compute is unavailable.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.