I’ve been living inside GitLab for most of my DevOps work — pushing code, wiring up pipelines, chasing down why a runner won’t pick up a job at 11 PM. Over time I built my own mental shortcut list, and this is that list, cleaned up and organized so you can actually use it as a reference instead of scrolling through old terminal history like I used to.
This covers everything from basic Git operations through GitLab, to CI/CD pipeline syntax, to the glab CLI, to security and troubleshooting. I’ve kept it practical — real commands, real examples, real gotchas.
Table of Contents
- Git & Repository Basics on GitLab
- Branching & Merge Requests
- GitLab CI/CD Fundamentals
.gitlab-ci.ymlSyntax Reference- Runners
- Variables & Secrets
- Container Registry & Docker
- GitLab CLI (
glab) Commands - Issues & Project Management
- Tags & Releases
- Security Features
- Best Practices
- Troubleshooting Common Issues
- Real-World Workflows
- FAQs
- Interview Questions
- Common Mistakes
- Printable Quick-Reference Summary
- Official Documentation Links
1. Git & Repository Basics on GitLab
Since GitLab is built on Git, the foundation is standard Git commands pointed at a GitLab remote.
| Command | Description | Example |
|---|---|---|
git clone | Clone a GitLab repo | git clone git@gitlab.com:group/project.git |
git remote -v | Show configured remotes | git remote -v |
git add | Stage changes | git add . |
git commit | Commit staged changes | git commit -m "Fix login bug" |
git push | Push to remote | git push origin main |
git pull | Fetch and merge | git pull origin main |
git fetch | Fetch without merging | git fetch origin |
git status | Show working tree status | git status |
git log | Show commit history | git log --oneline --graph |
I always clone with SSH rather than HTTPS when I can — it saves me from typing credentials on every push, assuming I’ve already got my SSH key added to my GitLab profile.
git clone git@gitlab.com:mygroup/myproject.git
Checking commit history the way I actually read it:
git log --oneline --graph --decorate --all
That gives me a compact, visual view of branches and merges instead of a huge wall of text.
2. Branching & Merge Requests
GitLab’s equivalent of GitHub’s “pull request” is the merge request (MR). Here’s how I typically work with branches.
| Command | Description | Example |
|---|---|---|
git branch | List branches | git branch -a |
git checkout -b | Create and switch to new branch | git checkout -b feature/login-fix |
git switch -c | Modern alternative to checkout -b | git switch -c feature/login-fix |
git merge | Merge a branch | git merge feature/login-fix |
git rebase | Reapply commits on top of another base | git rebase main |
git push -u origin <branch> | Push new branch and set upstream | git push -u origin feature/login-fix |
My typical merge request flow:
git checkout -b feature/add-search
# make changes
git add .
git commit -m "Add search functionality"
git push -u origin feature/add-search
After that push, GitLab gives me a direct link in the terminal output to open a merge request — I click it, fill in the description, assign a reviewer, and let CI run.
Naming conventions I stick to:
feature/short-descriptionbugfix/short-descriptionhotfix/short-descriptionchore/short-description
Consistent naming makes it much easier to scan a busy repo’s branch list.
3. GitLab CI/CD Fundamentals
This is where GitLab really shines compared to plain Git hosting. Every pipeline is defined in a .gitlab-ci.yml file at your repo root.
Core concepts:
- Pipeline — the full automated process triggered by a commit or MR.
- Stage — a phase in the pipeline (e.g.,
build,test,deploy). - Job — an individual task within a stage.
- Runner — the agent that actually executes jobs.
- Artifact — files produced by a job, passed to later stages or downloadable.
A minimal pipeline looks like this:
stages:
- build
- test
- deploy
build_job:
stage: build
script:
- echo "Building the application..."
- npm install
test_job:
stage: test
script:
- npm test
deploy_job:
stage: deploy
script:
- echo "Deploying to production..."
only:
- main
Jobs in the same stage run in parallel; stages run sequentially. So test_job won’t start until every job in build succeeds.
4. .gitlab-ci.yml Syntax Reference
| Keyword | Purpose | Example |
|---|---|---|
stages | Define pipeline stages in order | stages: [build, test, deploy] |
script | Commands to run in a job | script: - npm run build |
image | Docker image for the job | image: node:20 |
only / except | Legacy job filtering | only: [main] |
rules | Modern conditional job logic | rules: - if: '$CI_COMMIT_BRANCH == "main"' |
artifacts | Files to keep after job runs | artifacts: paths: [dist/] |
cache | Cache dependencies between runs | cache: paths: [node_modules/] |
before_script | Commands run before the main script | before_script: - npm ci |
after_script | Commands run after the main script | after_script: - echo "done" |
needs | Define job dependencies for DAG pipelines | needs: ["build_job"] |
environment | Track deployment environments | environment: production |
when | Control job execution condition | when: manual |
retry | Auto-retry failed jobs | retry: 2 |
timeout | Max job runtime | timeout: 30 minutes |
Using rules instead of only/except (the modern approach):
deploy_prod:
stage: deploy
script:
- ./deploy.sh production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
- when: never
I moved almost all my pipelines from only/except to rules a while back — rules gives far more control, especially when combining branch conditions with variables.
Caching dependencies to speed up pipelines:
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
That alone cut several of my Node.js pipelines from minutes down to seconds on repeat runs.
5. Runners
Runners are the machines (or containers) that actually execute your pipeline jobs.
| Command | Description | Example |
|---|---|---|
gitlab-runner register | Register a new runner | gitlab-runner register |
gitlab-runner list | List registered runners | gitlab-runner list |
gitlab-runner run | Start the runner process | gitlab-runner run |
gitlab-runner verify | Verify runner connectivity | gitlab-runner verify |
gitlab-runner unregister | Remove a runner | gitlab-runner unregister --name my-runner |
Registering a runner (interactive prompt walkthrough):
sudo gitlab-runner register
# Enter GitLab instance URL
# Enter registration token (from Project > Settings > CI/CD > Runners)
# Enter a description
# Enter tags (e.g., docker,linux)
# Enter executor (docker, shell, kubernetes, etc.)
I tag my runners deliberately (docker, linux, gpu, etc.) so I can target specific runners from within a job:
test_job:
tags:
- docker
script:
- npm test
Without matching tags, a job can sit “pending” forever because no runner picked it up — one of the most common pipeline issues I’ve had to debug for other people.
6. Variables & Secrets
| Type | Where Defined | Use Case |
|---|---|---|
| CI/CD variables | Project Settings > CI/CD > Variables | API keys, tokens, credentials |
| Predefined variables | Automatically set by GitLab | $CI_COMMIT_BRANCH, $CI_PROJECT_NAME |
| File-type variables | Settings, marked as “File” | Config files, certs |
| Protected variables | Only exposed on protected branches | Production secrets |
Common predefined variables I reference constantly:
$CI_COMMIT_BRANCH # current branch
$CI_COMMIT_SHA # full commit hash
$CI_PROJECT_NAME # project name
$CI_PIPELINE_ID # pipeline ID
$CI_JOB_NAME # current job name
$CI_ENVIRONMENT_NAME # deployment environment
Using a secret variable in a job:
deploy_job:
script:
- curl -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com/deploy
I always mark sensitive values as both “Protected” and “Masked” in the UI — protected so they only apply on protected branches, masked so they never show up in job logs.
7. Container Registry & Docker
GitLab ships with a built-in Container Registry per project.
| Command | Description | Example |
|---|---|---|
docker login | Authenticate to registry | docker login registry.gitlab.com |
docker build | Build an image | docker build -t registry.gitlab.com/group/project . |
docker push | Push image to registry | docker push registry.gitlab.com/group/project |
docker pull | Pull image from registry | docker pull registry.gitlab.com/group/project |
A typical build-and-push job inside .gitlab-ci.yml:
build_image:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
$CI_REGISTRY, $CI_REGISTRY_USER, and $CI_REGISTRY_PASSWORD are all predefined for you automatically — I don’t need to manually configure Docker credentials in most pipelines.
8. GitLab CLI (glab) Commands
glab is GitLab’s official command-line tool, similar in spirit to GitHub’s gh. I use it constantly to avoid tab-switching to the browser.
| Command | Description | Example |
|---|---|---|
glab auth login | Authenticate the CLI | glab auth login |
glab repo clone | Clone a repo | glab repo clone group/project |
glab mr create | Create a merge request | glab mr create --title "Fix bug" --description "..." |
glab mr list | List open MRs | glab mr list |
glab mr view | View MR details | glab mr view 42 |
glab mr merge | Merge an MR | glab mr merge 42 |
glab issue create | Create an issue | glab issue create --title "Bug: login fails" |
glab issue list | List issues | glab issue list |
glab pipeline list | List pipelines | glab pipeline list |
glab pipeline ci view | View pipeline in terminal | glab pipeline ci view |
glab ci trace | Stream job log live | glab ci trace |
Creating and merging an MR entirely from the terminal:
glab mr create --title "Add search filters" --description "Adds category and date filters"
glab mr view
glab mr merge --squash
I use glab ci trace a lot when I want to watch a currently running job’s log stream without opening a browser tab.
9. Issues & Project Management
GitLab’s issue tracker is tightly integrated with merge requests and boards.
| Feature | Description |
|---|---|
| Issue boards | Kanban-style visual tracking |
| Labels | Categorize and filter issues |
| Milestones | Group issues/MRs into a timeboxed goal |
| Weight | Assign relative effort/complexity to issues |
| Epics (Premium+) | Group related issues across projects |
Linking a commit to an issue automatically:
git commit -m "Fix login redirect, closes #45"
Using closes #45 (or fixes, resolves) in a commit message automatically closes issue #45 when that commit lands on the default branch.
10. Tags & Releases
| Command | Description | Example |
|---|---|---|
git tag | Create a lightweight tag | git tag v1.2.0 |
git tag -a | Create an annotated tag | git tag -a v1.2.0 -m "Release 1.2.0" |
git push --tags | Push all tags to remote | git push origin --tags |
glab release create | Create a GitLab release | glab release create v1.2.0 |
Tagging and pushing a release:
git tag -a v1.2.0 -m "Add search and performance fixes"
git push origin v1.2.0
I pair this with a release stage in CI that triggers only when a tag matching v* is pushed:
release_job:
stage: release
script:
- echo "Publishing release $CI_COMMIT_TAG"
rules:
- if: '$CI_COMMIT_TAG =~ /^v/'
11. Security Features
GitLab has strong built-in security tooling, especially from Ultimate/Premium tiers, but several are available even in lower tiers.
| Feature | Description |
|---|---|
| SAST | Static Application Security Testing on source code |
| Dependency Scanning | Flags vulnerable dependencies |
| Secret Detection | Scans for accidentally committed credentials |
| Container Scanning | Scans Docker images for known CVEs |
| Protected branches | Restrict who can push/merge to critical branches |
| Protected variables | Restrict secret exposure to protected branches/tags |
Enabling SAST with GitLab’s included template:
include:
- template: Security/SAST.gitlab-ci.yml
That one line pulls in a full static analysis job without me having to hand-write scanning logic myself.
12. Best Practices
- Keep
.gitlab-ci.ymlDRY usingextendsandincludefor shared job configs. - Use
rulesoveronly/exceptfor anything beyond the simplest branch filter. - Cache dependencies (
node_modules,.m2,vendor/) to cut pipeline time significantly. - Protect your
main/productionbranches and require MR approvals. - Use
needsto build a DAG pipeline instead of strictly sequential stages when jobs don’t actually depend on each other — it can meaningfully cut total pipeline time. - Mask and protect all sensitive CI/CD variables.
- Pin Docker image versions (
node:20.11rather thannode:latest) so pipelines don’t randomly break on upstream image updates. - Use merge request templates so every MR has consistent context (what changed, why, how it was tested).
13. Troubleshooting Common Issues
Pipeline stuck “pending” forever Usually means no runner matches the job’s tags, or all runners are busy/offline. Check Settings > CI/CD > Runners.
“This job could not start because it could not retrieve the job specification” Usually a runner-to-GitLab connectivity issue — check the runner’s network access and token validity.
Merge request shows conflicts Pull the target branch locally and rebase or merge:
git checkout feature/my-branch
git fetch origin
git merge origin/main
# resolve conflicts, then
git add .
git commit
git push
Docker-in-Docker (dind) job fails to connect to the daemon Make sure DOCKER_HOST, DOCKER_TLS_CERTDIR, and the docker:dind service are all correctly configured together — mismatches here are a very common source of failures.
Variable not being picked up in a job Check whether it’s marked “Protected” and whether the pipeline is actually running on a protected branch.
14. Real-World Workflows
Feature branch to production, start to finish:
git checkout -b feature/checkout-flow
# work, commit
git push -u origin feature/checkout-flow
glab mr create --title "New checkout flow"
# CI runs automatically; review comments addressed
glab mr merge --squash
git checkout main
git pull origin main
git tag -a v2.1.0 -m "Checkout flow release"
git push origin v2.1.0
Debugging a failing pipeline live:
glab pipeline list
glab pipeline ci view
glab ci trace
Rolling back a bad deploy using a manual job:
rollback:
stage: deploy
script:
- ./deploy.sh $LAST_STABLE_TAG
when: manual
15. FAQs
What’s the difference between GitLab CI/CD and Jenkins? GitLab CI/CD is built directly into the repository host and configured with a single YAML file per repo, while Jenkins is a separate, more general-purpose automation server that typically needs plugins and more manual wiring to connect to your source control.
Do I need a separate server to run GitLab CI/CD? You need at least one runner, but GitLab.com provides shared runners for free (with usage limits), so for many projects you don’t need to host your own.
What’s the difference between only/except and rules? rules is the newer, more flexible syntax that supports conditional logic (if, changes, exists), while only/except is simpler but more limited and considered legacy.
Can I run GitLab CI/CD pipelines locally before pushing? Yes — gitlab-runner exec docker <job-name> lets you simulate a job locally, though it doesn’t perfectly replicate the full remote environment.
How is glab different from using the GitLab web UI? glab lets you create, view, and merge MRs, manage issues, and watch pipelines entirely from the terminal, which is faster for people who live in a shell most of the day.
16. Interview Questions
- Explain the relationship between pipelines, stages, and jobs in GitLab CI/CD.
- What’s the difference between
artifactsandcachein a.gitlab-ci.ymlfile? - How would you set up a pipeline that only deploys to production on a manual trigger?
- What are protected branches and protected variables, and why do they matter together?
- How does
needschange the execution model of a pipeline compared to standard stages? - Walk through how you’d debug a job stuck in “pending” status.
- What is Docker-in-Docker (
dind), and why is it needed for building images in CI? - How do merge trains work, and what problem do they solve?
- What’s the difference between a lightweight tag and an annotated tag in Git?
- How would you structure a monorepo pipeline so unrelated services don’t trigger each other’s jobs?
17. Common Mistakes
- Forgetting to tag runners and jobs consistently, leaving jobs stuck pending.
- Using
only: [main]whenruleswould give far more precise control. - Committing secrets directly into
.gitlab-ci.ymlinstead of using masked CI/CD variables. - Not caching dependencies, leading to unnecessarily long pipeline times.
- Using
latesttags for Docker images in CI, causing unpredictable, hard-to-reproduce failures. - Forgetting
git push --tagsafter creating a tag locally, so the tag never reaches GitLab. - Merging without squashing on projects that expect a clean, linear history.
- Not setting
when: manualon production deploy jobs, letting a normal push accidentally trigger a live deployment.
18. Printable Quick-Reference Summary
CLONE/PUSH git clone git@gitlab.com:group/project.git
BRANCH git checkout -b feature/x, git push -u origin feature/x
MR (glab) glab mr create, glab mr view, glab mr merge --squash
PIPELINE BASICS stages, script, image, rules, artifacts, cache
RUNNERS gitlab-runner register / list / verify
VARIABLES $CI_COMMIT_BRANCH, $CI_PROJECT_NAME, masked/protected vars
DOCKER docker login $CI_REGISTRY, docker push $CI_REGISTRY_IMAGE
GLAB CLI glab auth login, glab pipeline list, glab ci trace
TAGS/RELEASES git tag -a v1.0.0 -m "msg", git push origin v1.0.0
SECURITY include: template: Security/SAST.gitlab-ci.yml
19. Official Documentation Links
- GitLab CI/CD Documentation
- GitLab CI/CD YAML Reference
- GitLab Runner Documentation
- glab CLI Documentation
- GitLab Container Registry Documentation
- GitLab Application Security Documentation
This is the exact reference I keep pinned when I’m setting up new projects or fixing a broken pipeline under pressure. GitLab has a lot of surface area, but once these commands and concepts are second nature, the whole CI/CD workflow stops feeling like a black box and starts feeling like just another tool in the kit.
