Level up your pipelines — secret variables, artifacts that pass files between jobs, rules that control when jobs run, deployment environments, and free hosting with GitLab Pages.
Why: API keys and deploy tokens must never be committed. Define them once in the project settings and every job receives them as environment variables. Note: check Masked so the value is hidden in job logs, and Protected so it only exists on protected branches like main.
# Define secrets in the UI: Settings → CI/CD → Variables
# ✓ Masked = hidden if it ever appears in a job log
# ✓ Protected = only available on protected branches like main
deploy:
script:
# Your variables arrive as environment variables in every job
- ./deploy.sh --token "$DEPLOY_TOKEN"
# GitLab also predefines many for you:
# $CI_COMMIT_BRANCH, $CI_PIPELINE_ID, $CI_PROJECT_NAME,
# $CI_MERGE_REQUEST_IID, $CI_COMMIT_SHORT_SHA, …Why: every job starts on a fresh machine, so whatever build-site compiled is gone when deploy-site starts — unless you save it as an artifact. Artifacts upload when a job ends, download into later jobs automatically, and can be browsed from the UI.
# .gitlab-ci.yml
stages:
- build
- deploy
build-site:
stage: build
image: node:22
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/ # saved when the job ends — downloadable from the UI too
expire_in: 1 week
deploy-site:
stage: deploy
script:
# dist/ from build-site is automatically restored here
- ls dist/Why: not every job belongs in every pipeline — tests should run for merge requests, deploys only from main. rules is a checklist evaluated top to bottom: the first matching line decides whether the job runs, and a job with no match is skipped.
# .gitlab-ci.yml
unit-tests:
script: npm test
rules:
# Run in merge request pipelines…
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# …and on pushes to main; any other push skips this job
- if: $CI_COMMIT_BRANCH == "main"
deploy:
script: ./deploy.sh
rules:
# Deploy ONLY from main
- if: $CI_COMMIT_BRANCH == "main"Why: tagging a deploy job with an environment makes GitLab track what is deployed where — Operate → Environments shows the current version of staging and production with links. when: manual turns a job into a button: the pipeline pauses and a human clicks to release.
# .gitlab-ci.yml
deploy-staging:
stage: deploy
script: ./deploy.sh staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy-production:
stage: deploy
script: ./deploy.sh production
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual # a button in the UI — a human clicks to releaseWhy: GitLab hosts static sites for free straight from your pipeline — perfect for a portfolio, docs, or any site built to plain HTML/CSS/JS. The rule: a job named pages publishes whatever ends up in the public folder to https://<username>.gitlab.io/<project>.
# .gitlab-ci.yml — deploy to https://<username>.gitlab.io/<project>
pages: # the job MUST be named "pages"
image: node:22
script:
- npm ci
- npm run build
- mv dist public # Pages serves the "public" folder
artifacts:
paths:
- public
rules:
- if: $CI_COMMIT_BRANCH == "main"