Aller au contenu
CI/CD & Automatisation medium

GitLab CI : branches protégées, environnements et approbations

17 min de lecture

Le déploiement en production doit être un acte délibéré, validé et traçable, pas un effet de bord d'un git push. GitLab propose quatre mécanismes de contrôle : branches protégées, tags protégés, environnements protégés et deployment freezes. Ce guide vous montre comment configurer chaque couche pour que seules les bonnes personnes puissent déployer le bon code au bon moment.

À la fin de ce guide, vous saurez :

  • Protéger une branche pour contrôler qui peut push et merge
  • Protéger les tags pour sécuriser les releases
  • Créer un environnement protégé avec approbation obligatoire
  • Configurer un deployment freeze pour bloquer les déploiements planifiés
  • Appliquer des push rules pour renforcer les conventions

Les mécanismes de protection de branches et d'environnements sont nécessaires dès que :

  • Votre pipeline déploie automatiquement vers un environnement (staging, production)
  • Plusieurs développeurs contribuent au même projet et les MR ne sont pas systématiquement revues
  • Vous devez prouver à un auditeur que le déploiement en production suit un processus de validation
  • Des périodes de gel de déploiement (release freeze, vacances, événements commerciaux) doivent être respectées
  • Vous publiez des releases via des tags et devez empêcher la création de tags par n'importe qui

Protéger une branche dissocie deux droits que GitLab accorde ensemble par défaut : le droit de pousser et le droit de merger. La configuration cible refuse le push à tout le monde, y compris aux Owners, et réserve le merge aux Maintainers ; toute modification passe alors obligatoirement par une merge request. La protection agit aussi sur les variables CI protégées, qui ne sont injectées que dans les pipelines s'exécutant sur une branche ou un tag protégé.

Sur un projet GitLab, un Maintainer peut pousser directement sur main sans passer par une MR. Un Developer peut merger une MR sans approbation. Il n'y a aucune barrière entre le code écrit et le code déployé.

La protection se pose au niveau projet et prend effet immédiatement, sans redémarrer quoi que ce soit. Interdire le force push est le réglage le plus important : sans lui, un historique réécrit fait disparaître des commits déjà revus.

  1. Accéder aux paramètres

    Settings → Repository → Protected branches.

  2. Protéger main

    ParamètreValeur recommandéeJustification
    BranchmainBranche de production
    Allowed to mergeMaintainersSeuls les maintainers mergent vers main
    Allowed to push and mergeNo onePersonne ne push directement
    Allowed to force pushNonUn force push réécrit l'historique
  3. Vérifier la protection

    Fenêtre de terminal
    # Via l'API GitLab
    curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
    "$GITLAB_URL/api/v4/projects/$PROJECT_ID/protected_branches"

    Résultat attendu :

    [
    {
    "name": "main",
    "push_access_levels": [{"access_level": 0}],
    "merge_access_levels": [{"access_level": 40}],
    "allow_force_push": false
    }
    ]

    access_level: 0 = No one, 40 = Maintainers.

Vous pouvez protéger plusieurs branches avec un wildcard :

Fenêtre de terminal
# Protéger toutes les branches release/*
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_URL/api/v4/projects/$PROJECT_ID/protected_branches" \
--data "name=release/*&push_access_level=0&merge_access_level=40"

Patterns courants :

PatternUsage
mainBranche de production
release/*Branches de release
hotfix/*Correctifs urgents
*-stableBranches LTS

Pour appliquer la même politique sur tous les projets d'un groupe :

#!/usr/bin/env bash
# protect-branches.sh : appliquer la politique de branches sur un groupe
GROUP_ID="${1:?Usage: $0 <group_id>}"
# Récupérer tous les projets du groupe
project_ids=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_URL/api/v4/groups/$GROUP_ID/projects?per_page=100" \
| jq -r '.[].id')
for pid in $project_ids; do
echo "Protection de main pour le projet $pid"
curl -s --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_URL/api/v4/projects/$pid/protected_branches" \
--data "name=main&push_access_level=0&merge_access_level=40&allow_force_push=false" \
| jq '{name, push_access_levels, merge_access_levels}'
done

Les tags se protègent séparément des branches, avec un seul droit à accorder : celui de créer un tag correspondant au motif. Un tag protégé devient également immuable pour la suppression par les profils non autorisés, ce qui évite qu'une release soit retirée puis republiée sur un autre commit avec le même numéro de version.

Un tag déclenche souvent un pipeline de release. Si n'importe quel Developer peut créer un tag v2.0.0, il peut déclencher un build et un déploiement non autorisé.

Le motif v* couvre l'ensemble de la convention vX.Y.Z en une seule règle et s'applique aux tags futurs, pas seulement à ceux qui existent déjà.

  1. Accéder aux paramètres

    Settings → Repository → Protected tags.

  2. Protéger le pattern de tags

    ParamètreValeur recommandée
    Tag patternv*
    Allowed to createMaintainers
  3. Vérifier via l'API

    Fenêtre de terminal
    curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
    "$GITLAB_URL/api/v4/projects/$PROJECT_ID/protected_tags"

    Résultat attendu :

    [
    {
    "name": "v*",
    "create_access_levels": [{"access_level": 40}]
    }
    ]

La protection d'environnement déplace le contrôle du dépôt vers la cible de déploiement : peu importe la branche d'origine, c'est l'accès à production qui est restreint. Elle complète les branches protégées sans les remplacer, et c'est le seul mécanisme des quatre qui introduit une validation humaine au milieu d'un pipeline en cours.

Un environnement protégé requiert une approbation explicite avant que le déploiement ne s'exécute. Le pipeline atteint le job de déploiement, puis se met en pause jusqu'à ce qu'un approbateur valide.

L'environnement doit exister avant de pouvoir être protégé, et GitLab ne le crée qu'au premier passage d'un job portant une clé environment:. La configuration se fait donc en deux temps : déclarer l'environnement dans le pipeline, puis le protéger dans l'interface.

  1. Déclarer l'environnement dans le pipeline

    .gitlab-ci.yml
    deploy-production:
    stage: deploy
    script:
    - kubectl apply -f manifests/
    environment:
    name: production
    url: https://app.example.com
    rules:
    - if: $CI_COMMIT_BRANCH == "main"
    resource_group: production
  2. Protéger l'environnement

    Settings → CI/CD → Environments → production → Edit → Protected environment.

    ParamètreValeur recommandée
    Allowed to deployMaintainers
    Required approvals1 (minimum)
    ApproversUtilisateurs ou groupes spécifiques
  3. Vérifier le comportement

    Poussez un commit sur main. Le pipeline doit atteindre le job deploy-production, afficher le statut blocked et attendre l'approbation dans l'interface.

Pour les environnements critiques, vous pouvez exiger plusieurs approbateurs et séparer les rôles :

Politique d'approbation recommandée
# staging : déploiement automatique, pas d'approbation
# pre-production : 1 approbation (lead technique)
# production : 2 approbations (lead technique + SRE)

Configurez via l'API :

Fenêtre de terminal
# Mettre à jour les approbations requises
curl --request PUT \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_URL/api/v4/projects/$PROJECT_ID/protected_environments/production" \
--data "required_approval_count=2"

GitLab attribue un tier à chaque environnement qui détermine son ordre d'affichage et ses comportements par défaut :

TierEnvironnements typiquesAuto-stop par défaut
productionproductionNon
stagingstaging, pre-prodNon
testingtest, qaOui (24h)
developmentdev, review/*Oui (24h)
otherTout le resteOui (24h)
.gitlab-ci.yml
deploy-staging:
environment:
name: staging
deployment_tier: staging

Le gel de déploiement ajoute une contrainte temporelle aux trois précédentes : les périodes se déclarent en expressions cron avec un fuseau horaire explicite, ce qui les rend auditables et évite la convention orale « on ne déploie pas le vendredi ». Attention à une subtilité : pendant la fenêtre, GitLab expose la variable $CI_DEPLOY_FREEZE, que vos jobs de déploiement doivent tester pour ne pas s'exécuter.

Un deployment freeze bloque tous les déploiements vers un environnement pendant une période définie. Les pipelines continuent de s'exécuter, mais les jobs de déploiement sont bloqués.

Une période de gel se décrit par deux expressions cron, début et fin, plus un fuseau horaire : sans lui, GitLab interprète les heures en UTC et le gel décale d'une ou deux heures selon la saison.

  1. Via l'interface

    Settings → CI/CD → Deploy freezes → Add deploy freeze.

    ParamètreExemple
    Freeze start0 18 * * 5 (vendredi 18h)
    Freeze end0 9 * * 1 (lundi 9h)
    TimezoneEurope/Paris
  2. Via l'API

    Fenêtre de terminal
    curl --request POST \
    --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
    "$GITLAB_URL/api/v4/projects/$PROJECT_ID/freeze_periods" \
    --data "freeze_start=0 18 * * 5&freeze_end=0 9 * * 1&cron_timezone=Europe/Paris"
  3. Vérifier les périodes actives

    Fenêtre de terminal
    curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
    "$GITLAB_URL/api/v4/projects/$PROJECT_ID/freeze_periods"

Ces quatre motifs couvrent les cas les plus fréquents. Rien n'empêche d'en déclarer plusieurs sur un même projet : un gel hebdomadaire et un gel commercial ponctuel se cumulent sans se gêner.

PériodeCron startCron endUsage
Week-end0 18 * * 50 9 * * 1Pas de déploiement le week-end
Nuit0 20 * * *0 7 * * *Pas de déploiement la nuit
Black Friday0 0 25 11 *0 0 2 12 *Gel e-commerce fin novembre
Fin de trimestre0 0 28 3,6,9,12 *0 0 2 4,7,10,1 *Gel comptable

Les push rules s'exécutent côté serveur GitLab, avant l'enregistrement des commits : un push refusé ne laisse aucune trace dans le dépôt et ne déclenche aucun pipeline. Elles s'appliquent à toutes les branches du projet, protégées ou non, ce qui en fait le complément naturel des sections précédentes. La plupart d'entre elles relèvent des éditions payantes.

Les push rules ajoutent des vérifications au moment du push, avant que le pipeline ne se déclenche :

Settings → Repository → Push rules.

RègleValeur recommandéeEffet
Reject unsigned commitsExige des commits signés GPG
Commit message regex^(feat|fix|docs|chore|refactor|test)(\(.+\))?: .+Force Conventional Commits
Branch name regex^(main|release\/.*|feature\/.*|hotfix\/.*)$Limite les noms de branches
File name regex (deny)\.(exe|dll|msi|pkg)$Bloque les binaires
Maximum file size (MB)10Empêche les gros fichiers

L'endpoint push_rule s'utilise en PUT sur un projet existant. Les expressions régulières doivent être encodées en URL : %2B remplace le + du motif, sinon curl l'envoie comme un espace et la règle rejette tous les messages de commit.

Fenêtre de terminal
curl --request PUT \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_URL/api/v4/projects/$PROJECT_ID/push_rule" \
--data "reject_unsigned_commits=true" \
--data "commit_message_regex=^(feat|fix|docs|chore|refactor|test)(\(.%2B\))?: .%2B" \
--data "max_file_size=10"

Une protection configurée dans l'interface se vérifie toujours par l'API : c'est la seule façon de prouver l'état réel, et le seul moyen de contrôler des dizaines de projets sans les ouvrir un par un. Les commandes ci-dessous supposent les variables GITLAB_TOKEN, GITLAB_URL et PROJECT_ID positionnées, avec un jeton portant le scope api.

ContrôleCommande de vérificationAttendu
main protégéecurl .../protected_branchespush_access_levels: [{access_level: 0}]
Force push interditidemallow_force_push: false
Tags v* protégéscurl .../protected_tagscreate_access_levels: [{access_level: 40}]
Env production protégéUI → EnvironmentsBadge "protected" visible
Approbation requiseUI → Environment settings≥ 1 required approval
Freeze week-end actifcurl .../freeze_periodsPériode vendredi-lundi
Push rules activescurl .../push_ruleRègles configurées

La cause la plus fréquente des incidents listés ici est un décalage entre ce qui est protégé et ce qui est réellement utilisé : une branche master protégée alors que le projet pousse sur main, ou un environnement nommé prod dans le pipeline et production dans les paramètres. Comparez toujours la valeur exacte, la correspondance est sensible à la casse.

SymptômeCause probableCorrection
Developer peut push sur mainmain non protégéeSettings → Repository → Protected branches → ajouter main
Pipeline deploy sans approbationEnvironnement non protégéSettings → CI/CD → Environments → Edit → cocher Protected
Variable protégée non injectéePipeline sur branche/tag non protégéVérifier que la branche ou le tag est dans la liste des éléments protégés
Tag v1.0.0 créé par un DeveloperTags non protégésSettings → Repository → Protected tags → ajouter v*
Deploy pendant un freezeJob sans règle $CI_DEPLOY_FREEZEAjouter rules: - if: $CI_DEPLOY_FREEZE / when: never au job
403 Forbidden sur l'API protected_branchesToken sans scope apiRecréer le token avec le scope api et le role Maintainer
Push rejeté : "commit not signed"reject_unsigned_commits activé sans GPG configuréConfigurer GPG sur le poste développeur ou désactiver temporairement la règle

Le contrôle de déploiement dans GitLab repose sur quatre couches complémentaires :

  1. Branches protégées : le verrou de base. Personne ne push directement sur main, seuls les Maintainers mergent. Sans ça, tout le reste est contournable.
  2. Tags protégés : le contrôle des releases. Si vos pipelines de release se déclenchent sur tag, protégez le pattern v* pour que seuls les Maintainers puissent taguer.
  3. Environnements protégés : le contrôle humain avant production. Le pipeline se met en pause sur le job de déploiement, un approbateur valide, le job reprend. C'est la différence entre "ça s'est déployé" et "on a décidé de déployer".
  4. Deployment freezes : le contrôle temporel. Les périodes sensibles (week-end, gel commercial, maintenance) sont protégées par configuration, pas par une convention orale.
  5. Push rules : le contrôle le plus précoce. Commits signés, conventions de nommage, taille de fichiers, ces vérifications s'exécutent côté serveur au moment du git push et rejettent le push avant qu'aucun pipeline ne démarre.

Les paramètres et niveaux d'accès de GitLab évoluent d'une version à l'autre : vérifiez ces pages officielles si un réglage ne correspond pas à votre instance.

Dix questions sur les niveaux d'accès, la portée réelle de chaque protection et le comportement des variables protégées.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

10 questions
5 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn