GitLab 09 🦊 Writing Your First .gitlab-ci.yml Pipeline Configuration
The previous chapter explained the architecture: stages, jobs, runners, and the needs keyword. This chapter builds the actual .gitlab-ci.yml file, line by line. The file is the single source of truth for the pipeline. It lives at the repository root, it is YAML, and it is parsed before any job runs. A syntax error prevents the pipeline from being created. The exam tests the essential keywords and the debugging commands. The critical distinction is between the global keywords (stages, variables, default) and the job keywords (stage, script, image, artifacts, rules). A job without a script is invalid. A job with a stage that is not in the stages list causes an error. A pipeline with no runners stays in the pending state.
Key point: The .gitlab-ci.yml file is validated with the CI Lint tool (CI/CD → Editor) before it is committed. The minimum valid file has one job with a script. The stages keyword is optional; if omitted, the default stages (build, test, deploy) are used. The file must be named exactly .gitlab-ci.yml and placed at the repository root.
Why .gitlab-ci.yml requires careful authoring
The YAML problem. YAML is whitespace-sensitive. A tab character in place of spaces causes a parse error. An unquoted colon in a string is interpreted as a key-value separator. A missing dash in a list is a syntax error. The pipeline editor in GitLab validates the syntax in real time, and it is the correct place to test the file before committing it .
The keyword problem. GitLab’s CI syntax has global keywords and job keywords. Placing a job keyword at the global level causes an error. Placing a global keyword inside a job causes it to be ignored. The script keyword is mandatory for every job. The stage keyword must match a stage in the stages list.
The stage-order problem. If a job declares stage: deploy but the stages list does not include deploy, the pipeline fails with a configuration error. The stages list defines the order. The stage keyword assigns a job to a stage. The two must agree.
The artifact problem. Artifacts are files produced by a job that are passed to subsequent jobs. The artifacts keyword specifies the paths. Without it, the file is available only within the job that created it. The dependencies keyword (or needs) controls which jobs receive the artifacts. The artifacts are uploaded after the job succeeds and downloaded before the dependent job starts .
The rules problem. The rules keyword replaces the older only and except keywords. It evaluates conditions in order. The first condition that matches determines whether the job runs. A job with no matching rule does not run. This is the mechanism for running jobs only on specific branches, tags, or merge requests.
The trade-off. A minimal file with one job and a simple script is easy to write. A file with multiple stages, artifacts, rules, and dependencies is more complex but necessary for a real pipeline. The exam tests the basic structure, not the advanced features.
a. The minimal valid pipeline
The smallest valid .gitlab-ci.yml has a single job with a script. Everything else is optional.
# .gitlab-ci.yml
job:
script:
- echo "Hello, GitLab"
The job name is job. The script is a list of shell commands. When committed to the repository, GitLab detects the file and creates a pipeline. The pipeline contains one job. The runner picks up the job, clones the repository, and executes the script. The job log shows the output .
The stages keyword is optional. If omitted, the default stages are build, test, and deploy. A job without a stage keyword is assigned to the test stage by default. This is a common source of confusion: a job that is intended to build is actually running in the test stage because the stage keyword was omitted .
# A job without a stage goes to "test"
build-job:
script:
- make build
The stage keyword makes the assignment explicit.
stages:
- build
- test
build-job:
stage: build
script:
- make build
test-job:
stage: test
script:
- make test
The pipeline runs build-job first, then test-job. If build-job fails, test-job does not run.
b. Images, artifacts, and variables
The image keyword specifies the container environment. The runner pulls the image and executes the script inside it. The before_script runs before the script. The after_script runs after the script, even if the job fails.
build-job:
stage: build
image: node:20
before_script:
- npm ci
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
The artifacts keyword saves the dist/ directory and makes it available to subsequent jobs. The expire_in setting controls how long GitLab retains the artifacts. The default is 30 days. The paths is a list of files or directories relative to the project directory .
The variables keyword defines environment variables. Global variables are available to every job. Job-level variables override global variables.
variables:
NODE_ENV: "production"
build-job:
stage: build
variables:
NODE_ENV: "staging"
script:
- echo "Environment: $NODE_ENV"
The job-level NODE_ENV overrides the global value. The script prints staging, not production.
The dependencies keyword (or needs) controls which jobs receive the artifacts. Without it, a job receives artifacts from the previous stage. With needs, it receives only the artifacts from the specified jobs.
test-job:
stage: test
needs: [build-job]
script:
- npm test
The test-job receives the dist/ directory from build-job. The other artifacts from the build stage are not downloaded.
c. Rules and conditional execution
The rules keyword determines whether a job runs. It is a list of conditions. Each condition has an if clause and an optional when clause. The first matching condition determines the outcome.
deploy-job:
stage: deploy
script:
- ./deploy.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: always
- when: never
The job runs if the commit is on the main branch. For any other branch, the second rule matches (when: never), and the job does not run. The when: never fallback is important: without it, a job with rules that do not match is excluded from the pipeline.
The rules keyword can also check the pipeline source: $CI_PIPELINE_SOURCE == "merge_request_event" runs the job only on merge request pipelines. The $CI_COMMIT_TAG variable matches tag pipelines.
test-mr:
script:
- npm test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
deploy-tag:
script:
- ./deploy.sh
rules:
- if: $CI_COMMIT_TAG
The test-mr job runs only on merge request events. The deploy-tag job runs only on tag pipelines. Neither job runs on a regular push.
Complete Example Session
# ============================================
# PART 1: MINIMAL PIPELINE
# ============================================
cat > .gitlab-ci.yml <<'EOF'
job:
script:
- echo "Hello, GitLab"
EOF
git add .gitlab-ci.yml
git commit -m "Add minimal CI"
git push origin main
# ============================================
# PART 2: VIEW THE PIPELINE
# ============================================
# Build → Pipelines
# The pipeline shows one job: "job".
# Click the job to see the log.
# ============================================
# PART 3: ADD STAGES
# ============================================
cat > .gitlab-ci.yml <<'EOF'
stages:
- build
- test
build-job:
stage: build
script:
- echo "Building..."
test-job:
stage: test
script:
- echo "Testing..."
EOF
# ============================================
# PART 4: ADD AN IMAGE
# ============================================
build-job:
stage: build
image: node:20
script:
- npm ci
- npm run build
# ============================================
# PART 5: ADD ARTIFACTS
# ============================================
build-job:
stage: build
script:
- make build
artifacts:
paths:
- dist/
expire_in: 1 week
# ============================================
# PART 6: ADD VARIABLES
# ============================================
variables:
NODE_ENV: "production"
build-job:
variables:
NODE_ENV: "staging"
script:
- echo "Environment: $NODE_ENV"
# ============================================
# PART 7: ADD RULES
# ============================================
deploy-job:
stage: deploy
script:
- ./deploy.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
- when: never
# ============================================
# PART 8: VALIDATE IN THE EDITOR
# ============================================
# Build → Pipeline editor
# Paste the YAML.
# The editor shows syntax errors and a visualization.
# ============================================
# PART 9: COMMIT AND VERIFY
# ============================================
git add .gitlab-ci.yml
git commit -m "Add complete CI pipeline"
git push origin main
# The pipeline runs. Check the status.
The nine parts trace the configuration: minimal file, view, stages, image, artifacts, variables, rules, validation, and commit.
Quick Reference
Essential Keywords
| Keyword | Level | Purpose |
|---|---|---|
stages | Global | Define stage order |
variables | Global or job | Environment variables |
default | Global | Default job settings |
job | Job | Job name |
stage | Job | Assign to stage |
script | Job | Commands to run |
image | Job | Container environment |
artifacts | Job | Files to save |
needs | Job | Job dependencies |
rules | Job | Conditional execution |
Artifact Options
| Option | Purpose |
|---|---|
paths | Files or directories to save |
expire_in | Retention time |
when | on_success, on_failure, always |
Rules Conditions
| Condition | Meaning |
|---|---|
if: $CI_COMMIT_BRANCH == "main" | Branch match |
if: $CI_COMMIT_TAG | Tag pipeline |
if: $CI_PIPELINE_SOURCE == "merge_request_event" | MR event |
when: never | Never run |
Best Practices
✅ Do This:
stages:
- build
- test
- ✅ Define the stage order explicitly
artifacts:
paths:
- dist/
- ✅ Pass artifacts to subsequent jobs
rules:
- if: $CI_COMMIT_BRANCH == "main"
- ✅ Gate deployment by branch
❌ Don’t Do This:
# Omit the stage keyword
<!-- Jobs go to "test" by default --> # ❌
# Hardcode secrets in .gitlab-ci.yml
<!-- Use protected variables --> # ❌
# Rely on the default stages without checking
<!-- The default is build, test, deploy --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Pipeline fails to create | YAML syntax error | Use the pipeline editor |
| Job in wrong stage | stage omitted | Add stage keyword |
| Artifact not available | artifacts not set | Add artifacts.paths |
| Job not running | rules excludes it | Check the if condition |
| Variable empty | Not defined or protected | Check project/group settings |
Real-World Examples
1. Minimal Job
job:
script:
- echo "Hello"
2. Staged Pipeline
stages:
- build
- test
build:
stage: build
script: make
test:
stage: test
script: make test
3. Image
build:
image: node:20
script: npm run build
4. Artifacts
build:
script: make build
artifacts:
paths:
- dist/
5. Global Variable
variables:
NODE_ENV: production
6. Job Variable
job:
variables:
NODE_ENV: staging
7. Rule for Main
deploy:
rules:
- if: $CI_COMMIT_BRANCH == "main"
8. Rule for Tag
deploy:
rules:
- if: $CI_COMMIT_TAG
9. Needs
test:
needs: [build]
10. Validate in Editor
Build → Pipeline editor
Visual
Pipeline Structure
┌──────────────────────────────────────────────┐
│ .gitlab-ci.yml │
│ │
│ stages: [build, test, deploy] │
│ │
│ build-job (stage: build) │
│ ├── image: node:20 │
│ ├── script: npm run build │
│ └── artifacts: dist/ │
│ │
│ test-job (stage: test) │
│ ├── needs: [build-job] │
│ └── script: npm test │
│ │
│ deploy-job (stage: deploy) │
│ ├── rules: main branch only │
│ └── script: ./deploy.sh │
└──────────────────────────────────────────────┘
Stage Flow
┌──────────────────────────────────────────────┐
│ build-job │
│ Produces artifacts (dist/) │
└──────────────────┬───────────────────────────┘
│ artifacts
▼
┌──────────────────────────────────────────────┐
│ test-job │
│ Receives artifacts │
└──────────────────┬───────────────────────────┘
│ needs
▼
┌──────────────────────────────────────────────┐
│ deploy-job (main only) │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| File location | Repository root |
| File name | .gitlab-ci.yml |
| Mandatory keyword | script |
| Default stages | build, test, deploy |
| Default stage | test (if omitted) |
| Artifact retention | 30 days (default) |
| Rules fallback | when: never |
| Validation | Pipeline editor |
Key takeaways:
- The
.gitlab-ci.ymlis the pipeline definition. It must be at the repository root, and it must be valid YAML. A syntax error prevents the pipeline from being created . - The
scriptis mandatory. A job without ascriptis invalid. Thestagekeyword assigns the job to a stage; if omitted, the job goes totest. - Artifacts pass files between jobs. The
artifactskeyword saves files from a job. Theneedsordependencieskeyword controls which jobs receive them . - Rules control execution. The
ruleskeyword evaluates conditions. A job with no matching rule does not run. Thewhen: neverfallback excludes the job for unmatched branches. - The pipeline editor validates. The CI Lint tool in the editor catches syntax errors before the commit.
Remember: The .gitlab-ci.yml is the configuration. The pipeline is the execution. The stages are the order. The jobs are the work. The artifacts are the handoff. The rules are the gate. Write the file, validate it, commit it, and check the pipeline. The file is the only artifact that matters.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!