Tutos

GitLab de A à Z : Git, runner et pipeline

Du premier commit au déploiement : clé SSH, projet, runner Docker sur ta VM, pipeline en trois étapes, variable secrète, branche et merge request.

  • Niveau débutant
  • 2 h
  • Mis à jour le 3 octobre 2026

On part de zéro et on arrive à un vrai cycle de travail : tu modifies du code sur ton poste, tu le pousses sur GitLab, ton propre runner exécute le pipeline, et tu passes par une branche et une merge request avant de déployer.

Ton posteGit : tu modifies, commites et pousses le code
git push
SSH
GitLabStocke le dépôt et lit .gitlab-ci.yml pour créer le pipeline
jobs
Ton runnerVM Debian avec Docker : exécute chaque job dans un conteneur
Ce qu'il te faut
  • Un compte GitLab : gitlab.com ou ton instance. Partout où tu vois gitlab.com, remplace par ton adresse si tu as ta propre instance.
  • Une VM Debian 12 ou 13 pour le runner, avec accès Internet. Celle du cours VM Debian sur Proxmox avec Terraform convient parfaitement.
  • Un poste Linux, macOS ou WSL pour Git.

Installer et configurer Git

Ton poste
sudo apt update && sudo apt install -y git
git --version

Git signe chaque commit avec un nom et un e-mail. Mets les mêmes que sur ton compte GitLab :

Ton poste
git config --global user.name "Jocelin Rey"
git config --global user.email "[email protected]"
git config --global init.defaultBranch main
git config --global --list
C'est bon si

git config --global --list affiche bien user.name, user.email et init.defaultbranch=main.

Ajouter ta clé SSH à GitLab

SSH évite de taper un mot de passe à chaque push. Si tu n'as pas encore de clé :

Ton poste
ssh-keygen -t ed25519 -C "jocelin@poste"
cat ~/.ssh/id_ed25519.pub

Copie toute la ligne affichée (elle commence par ssh-ed25519). Dans GitLab : Ton avatar (en haut à droite) › Edit profile › Access › SSH keys › Add new key.

Reproduction de l'écran GitLab (pas de capture officielle à jour). En rouge : ce que tu remplis.

Teste la connexion :

Ton poste
ssh -T [email protected]
Réponse attendue
Welcome to GitLab, @jrey!
Première connexion

SSH demande de confirmer l'empreinte du serveur : réponds yes. Elle est ensuite mémorisée dans ~/.ssh/known_hosts.

Créer le projet sur GitLab

+ (en haut de la barre latérale) › New project/repository › Create blank project.

Reproduction de l'écran GitLab (pas de capture officielle à jour). En rouge : ce que tu remplis.

GitLab ouvre la page du projet avec un premier commit (le README) :

Page d'un nouveau projet GitLab
Un projet tout juste créé : un commit, une branche, le README. Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.

Cloner le projet sur ton poste

Bouton Code (ou Clone selon la version), puis copie l'adresse Clone with SSH :

Menu Clone d'un projet GitLab
L'adresse SSH à copier (la première, en git@...). Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.
Ton poste
cd ~
git clone [email protected]:jrey/mon-site.git
cd mon-site
ls

Tu as maintenant une copie complète du dépôt, avec son historique. origin est le nom donné automatiquement au dépôt GitLab d'où tu as cloné.

Faire ton premier commit

Crée le fichier index.html à la racine du projet :

index.html
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Mon site</title>
</head>
<body>
  <h1>Bonjour depuis GitLab CI</h1>
</body>
</html>

Un fichier passe par trois zones avant d'arriver sur GitLab :

ZoneCe que c'estCommande pour y aller
Répertoire de travailTes fichiers tels que tu les édites(tu modifies)
Index (staging)Ce qui partira dans le prochain commitgit add
Dépôt localL'historique des commits, sur ton postegit commit
Dépôt distantLe projet sur GitLabgit push
Ton poste
git status
Sortie
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	index.html
Ton poste
git add index.html
git commit -m "Ajoute la page d'accueil"
git push
C'est bon si

Dans GitLab, la page du projet affiche index.html et ton message de commit. git status répond nothing to commit, working tree clean.

Installer le runner sur la VM Debian

Le runner est le programme qui exécute les jobs. On utilise l'exécuteur Docker : chaque job tourne dans un conteneur neuf, jeté à la fin. La VM reste propre.

Docker

VM runner
curl -fsSL https://get.docker.com | sudo sh
sudo docker run --rm hello-world

GitLab Runner

VM runner
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt install -y gitlab-runner
gitlab-runner --version
C'est bon si

hello-world affiche Hello from Docker! et gitlab-runner --version renvoie un numéro de version.

Créer le runner dans GitLab

Dans le projet : Settings › CI/CD › Runners (Expand) › Create project runner.

Reproduction de l'écran GitLab (pas de capture officielle à jour). En rouge : ce que tu remplis.

Choisis Linux. GitLab affiche la commande d'enregistrement avec le token du runner :

Reproduction de l'écran GitLab. En rouge : ce que tu copies.
Le token glrt- n'est affiché qu'une fois

Garde la page ouverte jusqu'à la fin de l'étape suivante. Si tu la fermes, supprime le runner et recrées-en un.

Enregistrer le runner

Sur la VM, colle ton token à la place de glrt-... :

VM runner
sudo gitlab-runner register --non-interactive \
  --url "https://gitlab.com" \
  --token "glrt-t1_xxxxxxxxxxxxxxxxxxxx" \
  --executor "docker" \
  --docker-image "alpine:3.22" \
  --name "runner-debian-01"
Sortie attendue
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

Le service tourne déjà (installé par le paquet). Vérifie :

VM runner
sudo gitlab-runner list
sudo gitlab-runner verify
systemctl status gitlab-runner --no-pager

Dans GitLab, Settings › CI/CD › Runners : ton runner apparaît avec une pastille verte Online. Sa fiche indique le dernier contact et la version :

Détails d'un runner GitLab
Last contact doit dater de quelques secondes. Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.
Sur gitlab.com : coupe les runners partagés

Gitlab.com fournit des instance runners qui pourraient prendre tes jobs à la place du tien. Dans la même page Runners, désactive Turn on instance runners for this project. Comme le pipeline met le tag debian sur tous les jobs, c'est ton runner qui travaille.

Écrire le pipeline

Crée .gitlab-ci.yml à la racine du projet (le point au début du nom est obligatoire) :

.gitlab-ci.yml
stages:
  - build
  - test
  - deploy

default:
  image: alpine:3.22
  tags:
    - debian

build-job:
  stage: build
  script:
    - echo "Construction du site pour le commit $CI_COMMIT_SHORT_SHA"
    - mkdir -p public
    - cp index.html public/
    - echo "$CI_COMMIT_SHORT_SHA" > public/version.txt
  artifacts:
    paths:
      - public/
    expire_in: 1 week

test-html:
  stage: test
  script:
    - test -f public/index.html
    - grep -q "<title>" public/index.html

test-version:
  stage: test
  script:
    - test -s public/version.txt
    - cat public/version.txt

deploy-prod:
  stage: deploy
  script:
    - echo "Déploiement de la version $(cat public/version.txt) sur $CI_ENVIRONMENT_NAME"
    - echo "Le jeton vaut $DEPLOY_TOKEN"
    - tar -czf site.tar.gz -C public .
  artifacts:
    paths:
      - site.tar.gz
  environment: production
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
Mot-cléRôle
stagesOrdre des étapes. Tous les jobs d'une étape doivent réussir pour passer à la suivante
defaultRéglages appliqués à tous les jobs : image Docker alpine et tag debian (donc ton runner)
scriptLes commandes du job, exécutées ligne par ligne. Une erreur arrête le job
artifactsFichiers conservés après le job et transmis aux étapes suivantes (ici le dossier public/)
Deux jobs en testtest-html et test-version tournent en parallèle
$CI_COMMIT_SHORT_SHAVariable fournie par GitLab : l'identifiant court du commit
environmentRattache le job à l'environnement production, visible dans Operate › Environments
rulesdeploy-prod n'existe que sur la branche principale, et doit être lancé à la main (when: manual)
Piège YAML : le deux-points

Une ligne de script qui contient : (deux-points suivi d'un espace) casse le YAML. Écris echo "Version 1" plutôt que echo "Version : 1", ou mets toute la ligne entre apostrophes.

Vérifier avant de pousser

Build › Pipeline editor affiche le fichier, signale les erreurs de syntaxe et dessine le pipeline (onglet Visualize).

Pousser et suivre le pipeline

Ton poste
git add .gitlab-ci.yml
git commit -m "Ajoute le pipeline CI"
git push

Dès le push, GitLab crée un pipeline. Ouvre Build › Pipelines :

Liste des pipelines GitLab
Chaque ligne est un pipeline : statut, commit, auteur et pastilles d'étapes. Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.

Clique sur le pipeline pour voir le graphe des étapes :

Graphe d'un pipeline GitLab en trois étapes
Exemple de la doc officielle : mêmes étapes build, test, deploy que notre fichier. Chez toi, les jobs s'appellent build-job, test-html, test-version et deploy-prod. Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.

Clique sur un job pour lire son journal :

Journal d'exécution d'un job
Le journal commence par le runner utilisé (vérifie que c'est runner-debian-01) et l'image Docker, puis chaque commande du script. Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.
StatutSignification
PendingLe job attend un runner. S'il y reste, voir « Si ça bloque »
RunningLe runner l'exécute
PassedToutes les commandes ont réussi
FailedUne commande a renvoyé une erreur : le journal montre laquelle
ManualJob à lancer à la main (notre deploy-prod)
C'est bon si

build-job, test-html et test-version sont en Passed, deploy-prod attend en Manual, et la page de build-job propose Browse / Download pour l'artefact public/.

Ajouter une variable secrète

Un mot de passe ou un token ne doit jamais être écrit dans le dépôt. On le range dans GitLab : Settings › CI/CD › Variables (Expand) › Add variable.

Reproduction de l'écran GitLab (pas de capture officielle à jour). En rouge : ce que tu remplis.

Le job deploy-prod l'utilise déjà avec $DEPLOY_TOKEN. Masquée, elle n'apparaîtra jamais en clair dans les journaux :

Journal de deploy-prod
$ echo "Le jeton vaut $DEPLOY_TOKEN"
Le jeton vaut [MASKED]

Lancer le déploiement manuel

Dans Build › Pipelines, le bouton ▶ de la ligne du pipeline liste les jobs manuels. Choisis deploy-prod :

Lancer un job manuel depuis la liste des pipelines
Le menu ▶ affiche les jobs manuels du pipeline, ici deploy-prod. Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.
Journal attendu
$ echo "Déploiement de la version $(cat public/version.txt) sur $CI_ENVIRONMENT_NAME"
Déploiement de la version 892bc2aa sur production
$ echo "Le jeton vaut $DEPLOY_TOKEN"
Le jeton vaut [MASKED]
$ tar -czf site.tar.gz -C public .
Job succeeded

Operate › Environments affiche maintenant production avec la version déployée et le commit correspondant.

Travailler sur une branche

On ne modifie pas main directement : on crée une branche, on y travaille, et on la fusionne une fois validée.

Ton poste
git switch main
git pull
git switch -c feature/nouveau-titre

Dans index.html, remplace le titre Bonjour depuis GitLab CI par Bienvenue sur mon site. Puis vérifie ce que tu as changé :

Ton poste
git status --short
git diff
Extrait de git diff
-  <h1>Bonjour depuis GitLab CI</h1>
+  <h1>Bienvenue sur mon site</h1>
Ton poste
git add index.html
git commit -m "Change le titre de la page"
git push -u origin feature/nouveau-titre

-u relie ta branche locale à celle de GitLab : les prochains git push et git pull sur cette branche n'auront plus besoin d'arguments. GitLab répond avec un lien direct pour créer la merge request :

Sortie de git push
remote: To create a merge request for feature/nouveau-titre, visit:
remote:   https://gitlab.com/jrey/mon-site/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fnouveau-titre
C'est bon si

Un pipeline se lance sur la branche avec 3 jobs seulement : deploy-prod n'existe pas hors de main, grâce à rules.

Ouvrir et fusionner une merge request

Ouvre le lien affiché par le push (ou Create merge request dans le bandeau du projet). Donne un titre, laisse Delete source branch coché :

Formulaire New merge request
Le formulaire de merge request, recadré (le pointillé marque la coupe). From = ta branche, into = main. Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.

Sur la page de la merge request : l'onglet Changes montre le diff, et le pipeline de la branche s'affiche en haut. Quand il est vert, clique sur Merge.

Merge request fusionnée
Après le merge : la branche source est supprimée et un nouveau pipeline démarre sur main. Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.
Pourquoi passer par une merge request ?

Le code est testé par le pipeline avant d'arriver sur main, quelqu'un peut le relire et commenter, et l'historique garde la trace de chaque changement et de sa raison.

Récupérer le résultat sur ton poste

Ton poste
git switch main
git pull
git branch -d feature/nouveau-titre
git fetch --prune
git log --oneline --graph -5
Sortie de git log
*   35a477d Merge branch 'feature/nouveau-titre' into 'main'
|\
| * 009afc5 Change le titre de la page
|/
* 892bc2a Ajoute le pipeline CI
CommandeRôle
git pullRécupère le commit de merge fait sur GitLab
git branch -dSupprime ta branche locale, devenue inutile (refuse si elle n'a pas été fusionnée)
git fetch --pruneOublie les branches supprimées côté GitLab

Le pipeline de main propose à nouveau deploy-prod : lance-le pour mettre en production la nouvelle version. La boucle est complète.

Corriger une erreur

SituationCommande
Annuler une modification pas encore ajoutéegit restore index.html
Retirer un fichier de l'index (après git add)git restore --staged index.html
Corriger le message ou ajouter un oubli au dernier commit, pas encore pousségit commit --amend
Annuler un commit déjà pousségit revert <id-du-commit> (crée un commit inverse, sans réécrire l'historique)
Mettre de côté des modifications pour changer de branchegit stash puis git stash pop
Ne réécris pas un historique déjà poussé

--amend, reset ou rebase sur des commits déjà sur GitLab obligent à forcer le push et cassent le travail des autres. Après un push, utilise git revert.

Aide-mémoire Git

CommandeCe qu'elle fait
git clone <url>Copie un dépôt distant sur ton poste
git statusÉtat des fichiers : modifiés, ajoutés, non suivis
git diffCe qui a changé depuis le dernier commit (--staged : ce qui est dans l'index)
git add <fichier>Prépare un fichier pour le commit (git add . : tout)
git commit -m "message"Enregistre un instantané dans l'historique local
git pushEnvoie tes commits sur GitLab
git pullRécupère et intègre les commits de GitLab
git switch -c <branche>Crée une branche et s'y place
git switch <branche>Change de branche
git branch -aListe les branches locales et distantes
git log --oneline --graphHistorique compact, avec les branches
git show <id>Détail d'un commit
git remote -vAdresse du dépôt distant
git tag v1.0 && git push --tagsMarque une version

Si ça bloque

Job en échec dans un pipeline
Survole un job en échec : GitLab donne la raison (ici une erreur de script). Le journal du job montre la commande fautive. Capture : documentation officielle GitLab, © GitLab Inc., CC BY-SA 4.0.
SymptômeCauseSolution
Permission denied (publickey) au pushClé SSH absente de GitLab ou mauvaise cléRefais l'étape 2 et teste avec ssh -T [email protected]
Job bloqué en Pending : no runners that match all of the job's tagsTag du job différent de celui du runner, ou runner hors ligneMême tag des deux côtés (debian) ; sudo gitlab-runner verify sur la VM
Runner gris (offline)Service arrêté ou VM sans accès à GitLabsudo systemctl restart gitlab-runner puis journalctl -u gitlab-runner -n 50
Pipeline invalide (yaml invalid)Indentation ou deux-points dans un scriptBuild › Pipeline editor pointe la ligne en erreur
Cannot connect to the Docker daemonDocker absent ou arrêté sur la VMsudo systemctl enable --now docker
! [rejected] ... (fetch first) au pushQuelqu'un a poussé avant toigit pull --rebase puis git push
Push refusé sur mainBranche protégéePasse par une branche et une merge request (étapes 13 et 14)
$DEPLOY_TOKEN vide sur une brancheVariable protégée : uniquement sur les branches protégéesNormal : elle ne sert qu'au déploiement depuis main