Aller au contenu
English
English
CI/CD & Automatisation medium

Variables et secrets GitHub Actions

Read this page in English

40 min de lecture

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.

  • 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_TOKEN et 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 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.

AspectVariablesSecrets
VisibilitéEn clair dans les logsMasquées (***)
ModificationUI, API, CLIUI, API, CLI
Accès${{ vars.NAME }}${{ secrets.NAME }}
UsageConfig, feature flagsTokens, passwords, keys

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.

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 workflow
env:
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"

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 :

  1. Repository → Settings → Secrets and variables → Actions
  2. Onglet « Variables »
  3. « New repository variable »

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 }}"

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.

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.

NiveauAccèsConfiguration
RepositoryCe repo uniquementSettings → Secrets
EnvironmentJobs avec cet environmentSettings → Environments
OrganizationTous les repos de l'orgOrg Settings → Secrets

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: false

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:.

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.sh

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.

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.sh

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.sh

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"

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."

Variables et secrets se combinent dans quelques schémas récurrents : configuration par environnement, feature flags, secrets multi-lignes et chargement depuis un fichier.

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"

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_FLAGS

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.json

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.

Quand une variable arrive vide ou inattendue, deux réflexes aident : inspecter l'environnement du runner et activer les logs détaillés.

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 | sort

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 step

Si 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.

Trois confusions reviennent sans cesse autour des variables et des secrets. Les reconnaître évite des heures de débogage.

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 env
jobs:
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 ».

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' }}"

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 workflow
env:
MY_VAR: value
steps:
- run: echo "${{ env.MY_VAR }}"
- run: echo "$MY_VAR" # Accès shell direct

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

6 questions
6 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

  • 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 dans run: ni passé en argument visible.
  • Le GITHUB_TOKEN est généré automatiquement pour chaque exécution ; déclarez ses permissions explicitement.
  • Ne jamais faire cat fichier >> $GITHUB_ENV en 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.

Ce site vous est utile ?

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

Je maintiens ce site gratuitement, sans publicité, sans profilage et sans compte à créer. 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