Les variables et secrets permettent de paramétrer vos workflows sans hardcoder les valeurs. Les variables servent aux configurations non sensibles ; les secrets aux données confidentielles, tokens, mots de passe, clés API.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Distinguer variables et secrets et savoir lequel utiliser
- Définir des variables aux quatre niveaux : workflow, job, step, configuration
- Consommer un secret sans jamais l'exposer dans les logs
- Comprendre le
GITHUB_TOKENet les secrets d'environnement - Faire circuler des valeurs entre steps et entre jobs via les outputs
- Éviter les pièges : injection par
GITHUB_ENV, secret vide sur les forks
Ce guide suppose que vous savez déjà écrire un workflow. Sinon, commencez par Workflows GitHub Actions.
Variables vs Secrets
Section intitulée « Variables vs Secrets »Variables et secrets se déclarent au même endroit et s'utilisent de façon similaire, mais leur traitement diffère radicalement : une variable s'affiche en clair, un secret est masqué par GitHub partout où il apparaîtrait. Le choix de l'un ou l'autre dépend donc de la sensibilité de la donnée.
| Aspect | Variables | Secrets |
|---|---|---|
| Visibilité | En clair dans les logs | Masquées (***) |
| Modification | UI, API, CLI | UI, API, CLI |
| Accès | ${{ vars.NAME }} | ${{ secrets.NAME }} |
| Usage | Config, feature flags | Tokens, passwords, keys |
Variables d'environnement
Section intitulée « Variables d'environnement »Une variable d'environnement rend une valeur disponible pour les commandes shell d'un step. GitHub Actions permet de les déclarer à plusieurs niveaux de portée, et propose aussi des variables de configuration persistantes.
Niveaux de définition
Section intitulée « Niveaux de définition »Les variables peuvent être définies à quatre niveaux, du plus large au plus précis. Le niveau le plus précis surcharge les niveaux supérieurs.
# 1. Niveau workflowenv: GLOBAL_VAR: 'workflow-level'
jobs: build: # 2. Niveau job env: JOB_VAR: 'job-level' runs-on: ubuntu-24.04 steps: # 3. Niveau step - name: Étape avec variables env: STEP_VAR: 'step-level' run: | echo "Global : $GLOBAL_VAR" echo "Job : $JOB_VAR" echo "Step : $STEP_VAR"Variables de configuration (vars)
Section intitulée « Variables de configuration (vars) »Les variables de configuration (vars) sont définies dans les paramètres
du repository, de l'environnement ou de l'organisation. Elles
persistent d'une exécution à l'autre, idéales pour une URL d'API ou un
feature flag.
steps: - name: Lire une variable de configuration run: | echo "Environnement : ${{ vars.ENVIRONMENT }}" echo "URL API : ${{ vars.API_URL }}" echo "Feature flag : ${{ vars.ENABLE_NEW_FEATURE }}"Configurer une variable :
- Repository → Settings → Secrets and variables → Actions
- Onglet « Variables »
- « New repository variable »
Variables dynamiques (outputs)
Section intitulée « Variables dynamiques (outputs) »Quand une valeur n'est connue qu'à l'exécution, numéro de version, empreinte
de commit, horodatage, un step la calcule et l'écrit dans $GITHUB_OUTPUT.
Les steps suivants la relisent avec steps.<id>.outputs.<nom>.
steps: - name: Générer les variables id: vars run: | echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT" echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" echo "timestamp=$(date +%Y%m%d%H%M%S)" >> "$GITHUB_OUTPUT"
- name: Réutiliser les variables run: | echo "Version : ${{ steps.vars.outputs.version }}" echo "SHA : ${{ steps.vars.outputs.sha_short }}" echo "Horodatage : ${{ steps.vars.outputs.timestamp }}"Variables entre jobs
Section intitulée « Variables entre jobs »Pour qu'un job transmette une valeur à un autre, il la publie dans son bloc
outputs:. Le job destinataire le déclare en needs:, ce qui crée à la
fois la dépendance d'ordre et l'accès à needs.<job>.outputs.<nom>.
jobs: setup: runs-on: ubuntu-24.04 outputs: version: ${{ steps.version.outputs.value }} steps: - name: Déterminer la version id: version run: echo "value=1.2.3" >> "$GITHUB_OUTPUT"
build: needs: setup runs-on: ubuntu-24.04 steps: - run: echo "Build de la version ${{ needs.setup.outputs.version }}"Un secret est une valeur confidentielle que GitHub chiffre au repos et
masque dans les logs. Cette section couvre leurs niveaux de portée, la
façon de les consommer et le cas particulier du GITHUB_TOKEN.
Types de secrets
Section intitulée « Types de secrets »Comme les variables, les secrets se déclarent à trois portées : le dépôt, l'environnement et l'organisation. Plus la portée est large, plus la surface d'exposition grandit : retenez la plus restreinte qui réponde au besoin.
| Niveau | Accès | Configuration |
|---|---|---|
| Repository | Ce repo uniquement | Settings → Secrets |
| Environment | Jobs avec cet environment | Settings → Environments |
| Organization | Tous les repos de l'org | Org Settings → Secrets |
Utiliser un secret
Section intitulée « Utiliser un secret »Un secret se consomme toujours via un bloc env: : la valeur devient une
variable d'environnement que le script lit, sans jamais figurer dans la
ligne de commande. Certaines actions acceptent aussi un jeton comme
paramètre with:, ce qui revient au même du point de vue du runner.
steps: # Méthode recommandée : passer le secret par un bloc env: - name: Déployer l'application env: API_KEY: ${{ secrets.API_KEY }} run: ./deploy.sh
# Certaines actions attendent un jeton comme paramètre with: - name: Cloner un dépôt privé uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: mon-org/depot-prive token: ${{ secrets.REPO_ACCESS_TOKEN }} persist-credentials: falseLe secret GITHUB_TOKEN
Section intitulée « Le secret GITHUB_TOKEN »Le GITHUB_TOKEN est un secret automatique : GitHub le génère au
démarrage de chaque exécution et le révoque à la fin. Vous n'avez rien à
créer, il est toujours là, et ses droits se règlent par le bloc
permissions:.
steps: - name: Récupérer le code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Créer une issue env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh issue create \ --title "Incident détecté par le workflow" \ --body "Issue ouverte automatiquement par la CI."Ses permissions dépendent de la configuration du workflow et du repository,
déclarez-les explicitement avec un bloc permissions:.
Secrets d'environnement
Section intitulée « Secrets d'environnement »Pour un déploiement, préférez les secrets attachés à un environnement : ils ne sont injectés que dans les jobs qui s'y rattachent, et cet environnement peut exiger une approbation manuelle ou n'accepter que certaines branches.
jobs: deploy-staging: environment: staging runs-on: ubuntu-24.04 steps: - name: Déployer en staging env: # Secret spécifique à l'environnement "staging" DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} run: ./deploy.sh
deploy-production: environment: production runs-on: ubuntu-24.04 steps: - name: Déployer en production env: # Secret différent, rattaché à l'environnement "production" DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} run: ./deploy.shBonnes pratiques de sécurité
Section intitulée « Bonnes pratiques de sécurité »Quelques réflexes évitent les fuites les plus courantes. Le principe de
fond, ne jamais interpoler un secret dans un run:, est introduit dans
Sécuriser GitHub Actions : les secrets ;
les pratiques ci-dessous l'appliquent au quotidien.
Ne jamais exposer un secret
Section intitulée « Ne jamais exposer un secret »Un secret interpolé dans un run: n'apparaît pas en clair dans les journaux,
où GitHub écrit *** : il devient un argument de commande, visible dans la
liste des processus du runner. Et le masquage ne couvre que la valeur
exacte, jamais ce qu'on en dérive. Passez-le par env:, et laissez le
script le lire.
# ❌ DANGEREUX : secret visible dans les logs- run: echo ${{ secrets.API_KEY }}- run: curl -H "Authorization: Bearer ${{ secrets.NPM_TOKEN }}" https://registry.npmjs.org/-/whoami
# ❌ DANGEREUX : secret copié dans une variable shell affichée- run: | KEY=${{ secrets.API_KEY }} echo "Clé utilisée : $KEY"
# ✅ SÉCURISÉ : passer le secret via env, le script le lit- name: Appeler l'API env: API_KEY: ${{ secrets.API_KEY }} run: ./script.shMasquer les valeurs dynamiques
Section intitulée « Masquer les valeurs dynamiques »Une valeur sensible générée à l'exécution, jeton temporaire ou mot de passe
tiré au sort, est inconnue de GitHub : rien ne la masque. La commande
::add-mask:: l'ajoute explicitement à la liste des valeurs censurées
dans les journaux, pour la suite du job.
- name: Générer le jeton id: token run: | TOKEN=$(./generate-token.sh) echo "::add-mask::$TOKEN" echo "token=$TOKEN" >> "$GITHUB_OUTPUT"
- name: Utiliser le jeton env: TOKEN: ${{ steps.token.outputs.token }} run: ./use-token.shLimiter l'accès aux secrets
Section intitulée « Limiter l'accès aux secrets »Les secrets ne sont pas exposés aux pull requests venant d'un fork : un
contributeur externe ne peut donc pas les exfiltrer par une contribution. Sur un
job sensible, vérifiez malgré tout l'origine de l'exécution, car
certains déclencheurs, pull_request_target en tête, changent cette règle.
jobs: build: # Ce job s'exécute sur le dépôt principal : secrets disponibles runs-on: ubuntu-24.04 steps: - name: Étape avec secret env: SECRET: ${{ secrets.MY_SECRET }} run: echo "Secret disponible"
build-fork: # On vérifie que la PR ne provient pas d'un fork if: github.event.pull_request.head.repo.fork == false runs-on: ubuntu-24.04 steps: - name: Étape conditionnée env: SECRET: ${{ secrets.MY_SECRET }} run: echo "Origine sûre, secret disponible"Planifier la rotation des secrets
Section intitulée « Planifier la rotation des secrets »Un secret qui n'est jamais renouvelé finit par fuiter, et personne n'y pense
spontanément. Un workflow planifié peut porter ce rappel en ouvrant une
issue à échéance. Comme il écrit dans le dépôt, il déclare la permission
minimale issues: write, et rien d'autre.
name: Rappel de rotation des secrets
on: schedule: - cron: '0 9 1 */3 *' # Le 1er de chaque trimestre, à 9h
permissions: {}
jobs: remind: runs-on: ubuntu-24.04 permissions: issues: write steps: - name: Créer l'issue de rappel env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh issue create \ --title "Rotation des secrets requise" \ --body "Il est temps de faire tourner les secrets du dépôt."Patterns courants
Section intitulée « Patterns courants »Variables et secrets se combinent dans quelques schémas récurrents : configuration par environnement, feature flags, secrets multi-lignes et chargement depuis un fichier.
Configuration par environnement
Section intitulée « Configuration par environnement »Une matrice sur les environnements permet de déployer staging et production
avec le même job, chacun recevant ses propres vars et secrets.
env: APP_NAME: my-app
jobs: deploy: runs-on: ubuntu-24.04 strategy: matrix: environment: [staging, production] environment: ${{ matrix.environment }} steps: - name: Déployer sur la cible env: TARGET_ENV: ${{ matrix.environment }} # Variables spécifiques à l'environnement (définies dans vars) API_URL: ${{ vars.API_URL }} REPLICAS: ${{ vars.REPLICAS }} # Secret spécifique à l'environnement DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} run: | echo "Déploiement de $APP_NAME vers $TARGET_ENV" echo "URL API : $API_URL" echo "Replicas : $REPLICAS"Feature flags
Section intitulée « Feature flags »Des variables de configuration servent d'interrupteurs de build : on les
lit via env:, puis le script compose les options en conséquence.
jobs: build: runs-on: ubuntu-24.04 steps: - name: Récupérer le code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Construire avec les options actives env: ENABLE_FEATURE_X: ${{ vars.ENABLE_FEATURE_X }} ENABLE_FEATURE_Y: ${{ vars.ENABLE_FEATURE_Y }} run: | BUILD_FLAGS="" if [ "$ENABLE_FEATURE_X" = "true" ]; then BUILD_FLAGS="$BUILD_FLAGS --feature-x" fi if [ "$ENABLE_FEATURE_Y" = "true" ]; then BUILD_FLAGS="$BUILD_FLAGS --feature-y" fi npm run build -- $BUILD_FLAGSSecrets multi-lignes
Section intitulée « Secrets multi-lignes »Une clé SSH, un certificat ou un JSON se collent tels quels dans
l'interface GitHub. Dans le workflow, on les écrit dans un fichier via env:,
avec des permissions strictes.
- name: Préparer la clé SSH env: SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }} run: | mkdir -p ~/.ssh echo "$SSH_KEY" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa
- name: Préparer les identifiants GCP env: GCP_CREDENTIALS: ${{ secrets.GCP_CREDENTIALS }} run: | echo "$GCP_CREDENTIALS" > /tmp/gcp-key.json chmod 600 /tmp/gcp-key.json export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp-key.jsonCharger des variables depuis un fichier
Section intitulée « Charger des variables depuis un fichier »Un fichier .env versionné peut alimenter $GITHUB_ENV, ce qui est
commode. Mais ne le recopiez jamais en aveugle : un contenu non maîtrisé
peut y glisser des variables qui changent le comportement du runner, comme
PATH ou NODE_OPTIONS, et faire exécuter du code arbitraire.
# ❌ Dangereux : tout le fichier injecté sans contrôle- run: cat .env >> "$GITHUB_ENV"
# ✅ Sûr : lire explicitement les clés attendues- name: Charger la version depuis le fichier run: | VERSION=$(grep '^VERSION=' .env | cut -d= -f2) echo "APP_VERSION=$VERSION" >> "$GITHUB_ENV"Alimenter GITHUB_ENV avec des données non fiables est une injection
d'environnement, le mécanisme et ses parades sont détaillés dans
Contexts et expressions.
Débogage
Section intitulée « Débogage »Quand une variable arrive vide ou inattendue, deux réflexes aident : inspecter l'environnement du runner et activer les logs détaillés.
Inspecter les variables disponibles
Section intitulée « Inspecter les variables disponibles »Un step de diagnostic affiche le contexte GitHub et les variables
d'environnement du job. Notez que les valeurs du contexte y transitent par
env: : c'est la même règle que partout ailleurs, et elle vaut aussi pour un
step qui ne fait qu'afficher.
- name: Inspecter l'environnement env: GH_REPOSITORY: ${{ github.repository }} GH_REF: ${{ github.ref }} GH_EVENT: ${{ github.event_name }} ALL_VARS: ${{ toJSON(vars) }} run: | echo "=== Contexte GitHub ===" echo "Dépôt : $GH_REPOSITORY" echo "Ref : $GH_REF" echo "Événement : $GH_EVENT" echo "=== Variables de configuration ===" echo "$ALL_VARS" echo "=== Variables d'environnement ===" env | sortActiver les logs détaillés
Section intitulée « Activer les logs détaillés »Deux réglages font passer GitHub Actions en mode verbeux : l'un couvre le runner, l'autre chaque step. Réservez-les au diagnostic, ils alourdissent beaucoup les journaux.
Ils ne se posent pas dans le workflow. C'est le point qui fait perdre du
temps : un bloc env: dans le YAML n'a aucun effet sur eux, parce que le
runner les lit avant de commencer à exécuter le fichier. Ils se déclarent
donc comme secret ou comme variable du dépôt :
Settings → Secrets and variables → Actions → Variables ACTIONS_RUNNER_DEBUG = true # journal détaillé du runner ACTIONS_STEP_DEBUG = true # journal détaillé de chaque stepSi le même nom existe des deux côtés, le secret l'emporte sur la variable. Pour un besoin ponctuel, le bouton « Re-run with debug logging » d'une exécution donne le même résultat sans rien configurer, et sans laisser le mode verbeux actif derrière vous.
Erreurs courantes
Section intitulée « Erreurs courantes »Trois confusions reviennent sans cesse autour des variables et des secrets. Les reconnaître évite des heures de débogage.
Secret non disponible
Section intitulée « Secret non disponible »Sur une PR de fork, les secrets sont volontairement vides. Un step qui en dépend doit le vérifier plutôt que d'échouer silencieusement.
# ❌ Le secret est vide sur les PR de forks- run: echo "Secret : ${{ secrets.MY_SECRET }}" # Résultat : "Secret : " (vide)
# ✅ Le secret passe par env: au niveau du job, puis se teste depuis envjobs: deploy: runs-on: ubuntu-24.04 env: MY_SECRET: ${{ secrets.MY_SECRET }} steps: - name: Vérifier la présence du secret if: env.MY_SECRET != '' run: echo "Secret disponible"Le contexte secrets n'existe pas dans un if:, ni sur un step, ni sur un
job : GitHub refuse le fichier avant de l'exécuter. La liste des contextes
autorisés dans jobs.<job_id>.steps.if est fermée, et elle contient env,
github, needs, matrix, runner, steps, vars, job, strategy et
inputs, mais pas secrets. D'où la forme ci-dessus : on fait descendre la
valeur dans env:, puis on teste env.MY_SECRET.
actionlint attrape l'erreur avant le premier push, avec un message qui nomme la cause : « context "secrets" is not allowed here ».
Variable non définie
Section intitulée « Variable non définie »Une variable de configuration absente renvoie une chaîne vide, pas une
erreur. Prévoyez une valeur par défaut avec l'opérateur ||.
# ❌ Chaîne vide si vars.OPTIONAL n'existe pas- run: echo "${{ vars.OPTIONAL }}"
# ✅ Valeur par défaut explicite- run: echo "${{ vars.OPTIONAL || 'default-value' }}"Confondre env et vars
Section intitulée « Confondre env et vars »vars désigne la configuration du dépôt, saisie dans l'onglet Settings ;
env désigne une variable d'environnement déclarée dans le workflow ou
le job. Les deux notations se ressemblent et ne désignent pas la même chose : un
vars.X absent rend une chaîne vide, sans erreur.
# vars = configuration repository (définie dans Settings)- run: echo "${{ vars.API_URL }}"
# env = variable d'environnement du workflowenv: MY_VAR: valuesteps: - run: echo "${{ env.MY_VAR }}" - run: echo "$MY_VAR" # Accès shell directContrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
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
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »- Variables = configuration non sensible, visible dans les logs ; secrets = données confidentielles, masquées automatiquement par GitHub.
- Les variables se définissent à quatre niveaux, workflow, job, step, configuration (
vars), le plus précis l'emporte. - Un secret se consomme toujours via un bloc
env:, jamais interpolé en clair dansrun:ni passé en argument visible. - Le
GITHUB_TOKENest généré automatiquement pour chaque exécution ; déclarez ses permissions explicitement. - Ne jamais faire
cat fichier >> $GITHUB_ENVen aveugle : un contenu non maîtrisé injecte des variables et peut exécuter du code. - Sur les PR de forks, les secrets sont vides, préférez les secrets d'environnement et conditionnez les jobs sensibles.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Workflows réutilisables : Transmettre nommément secrets et inputs à un workflow appelé, sans
secrets: inherit. - Sécuriser GitHub Actions : Le durcissement complet, des permissions du token à l'épinglage des actions.
- OIDC : déployer sans secret : Supprimer les credentials cloud longue durée plutôt que chercher à les protéger.