Les workflows réutilisables permettent de factoriser la logique CI/CD commune. Au lieu de copier-coller les mêmes jobs dans 10 repositories, vous créez un workflow central appelable par tous les autres.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Créer un workflow réutilisable avec le trigger
workflow_call - Définir des inputs, des secrets et des outputs typés
- Appeler un workflow réutilisable depuis un autre dépôt ou en local
- Passer les secrets explicitement ou via
secrets: inherit - Chaîner et versionner vos templates CI/CD
- Connaître les limitations : profondeur d'appel, secrets dynamiques
Ce guide s'adresse à ceux qui maintiennent plusieurs pipelines. Si vous débutez, voyez d'abord Workflows GitHub Actions.
Pourquoi réutiliser des workflows ?
Section intitulée « Pourquoi réutiliser des workflows ? »Problème : vous avez 50 microservices avec des pipelines quasi identiques. Chaque modification (nouvelle version de Node, ajout d'un scan de sécurité) nécessite de modifier 50 fichiers.
Solution : un workflow réutilisable dans un repository central :
org/ci-templates/└── .github/workflows/ ├── node-ci.yml # CI standard Node.js ├── python-ci.yml # CI standard Python └── docker-build.yml # Build et push d'imagesChaque microservice appelle ces templates :
# Dans chaque repo : 5 lignes au lieu de 100jobs: ci: uses: org/ci-templates/.github/workflows/node-ci.yml@v1.0.0 with: node-version: '20' secrets: inheritCréer un workflow réutilisable
Section intitulée « Créer un workflow réutilisable »Un workflow réutilisable est un fichier de workflow comme les autres, à une différence près : son déclencheur le rend appelable par d'autres workflows.
Structure de base
Section intitulée « Structure de base »Un workflow réutilisable utilise le trigger workflow_call :
name: Reusable CI
on: workflow_call: # Ce trigger rend le workflow appelable inputs: node-version: description: 'Version de Node.js' required: false type: string default: '20' secrets: npm-token: description: 'Token NPM pour publish' required: false
jobs: build: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ inputs.node-version }}
- run: npm ci - run: npm testDéfinir les inputs
Section intitulée « Définir les inputs »Les inputs permettent de paramétrer le workflow :
on: workflow_call: inputs: # Input obligatoire environment: description: 'Environnement de déploiement' required: true type: string
# Input optionnel avec valeur par défaut node-version: description: 'Version de Node.js' required: false type: string default: '20'
# Input booléen skip-tests: description: 'Skip les tests' required: false type: boolean default: false
# Input numérique timeout: description: 'Timeout en minutes' required: false type: number default: 30Types d'inputs disponibles : string, boolean, number.
Définir les secrets
Section intitulée « Définir les secrets »Les secrets sont déclarés séparément des inputs :
on: workflow_call: secrets: # Secret obligatoire deploy-key: description: 'Clé SSH de déploiement' required: true
# Secret optionnel slack-webhook: description: 'Webhook Slack pour notifications' required: falseDéfinir les outputs
Section intitulée « Définir les outputs »Les outputs permettent de retourner des valeurs au workflow appelant :
on: workflow_call: outputs: version: description: 'Version buildée' value: ${{ jobs.build.outputs.version }} image-digest: description: "Digest de l'image Docker" value: ${{ jobs.build.outputs.digest }}
jobs: build: runs-on: ubuntu-24.04 outputs: version: ${{ steps.version.outputs.value }} digest: ${{ steps.push.outputs.digest }} steps: - id: version run: echo "value=$(cat VERSION)" >> $GITHUB_OUTPUT
- id: push run: echo "digest=sha256:abc123" >> $GITHUB_OUTPUTAppeler un workflow réutilisable
Section intitulée « Appeler un workflow réutilisable »Côté appelant, un workflow réutilisable s'invoque à la place des steps: d'un
job, avec le mot-clé uses:.
Syntaxe d'appel
Section intitulée « Syntaxe d'appel »Le job appelant ne contient pas de steps: — il délègue entièrement le
travail au workflow réutilisable.
name: CI
on: [push, pull_request]
jobs: call-workflow: uses: owner/repo/.github/workflows/reusable.yml@v1.0.0 with: node-version: '20' environment: 'production' secrets: deploy-key: ${{ secrets.DEPLOY_KEY }}Référencer le workflow
Section intitulée « Référencer le workflow »Plusieurs formats sont possibles :
# Même repository (chemin relatif)uses: ./.github/workflows/reusable.yml
# Repository externe avec branche (mobile, à éviter en production)uses: org/repo/.github/workflows/reusable.yml@main
# Repository externe avec taguses: org/repo/.github/workflows/reusable.yml@v1.0.0
# Repository externe avec SHA (recommandé pour la sécurité)uses: org/repo/.github/workflows/reusable.yml@a1b2c3d4e5f6Passer les secrets
Section intitulée « Passer les secrets »Trois méthodes existent pour passer les secrets au workflow réutilisable.
1. Secrets explicites — vous transmettez nommément ce dont le template a besoin, et rien d'autre :
jobs: call: uses: org/repo/.github/workflows/reusable.yml@v1.0.0 secrets: npm-token: ${{ secrets.NPM_TOKEN }} deploy-key: ${{ secrets.DEPLOY_KEY }}2. Hériter tous les secrets — secrets: inherit transmet l'intégralité des
secrets du dépôt parent. Pratique, mais c'est l'option la plus large :
jobs: call: uses: org/repo/.github/workflows/reusable.yml@v1.0.0 secrets: inherit3. Aucun secret — si le workflow réutilisable n'en a pas besoin, n'en passez aucun :
jobs: call: uses: org/repo/.github/workflows/reusable.yml@v1.0.0Utiliser les outputs
Section intitulée « Utiliser les outputs »Le workflow appelant récupère les valeurs retournées par le workflow
réutilisable via needs.<job>.outputs.
jobs: build: uses: org/repo/.github/workflows/build.yml@v1.0.0
deploy: needs: build runs-on: ubuntu-24.04 steps: - run: | echo "Deploying version ${{ needs.build.outputs.version }}" echo "Image digest: ${{ needs.build.outputs.image-digest }}"Exemple complet
Section intitulée « Exemple complet »Voici un cas réel de bout en bout : un template de CI Node.js et le workflow d'un service qui l'appelle.
Workflow réutilisable (template)
Section intitulée « Workflow réutilisable (template) »Ce template centralise le lint et les tests Node.js, pilotables par des inputs.
name: Node.js CI
on: workflow_call: inputs: node-version: type: string default: '20' working-directory: type: string default: '.' run-tests: type: boolean default: true run-lint: type: boolean default: true secrets: npm-token: required: false outputs: test-passed: description: 'Tests passés' value: ${{ jobs.test.outputs.passed }}
permissions: contents: read
jobs: lint: if: ${{ inputs.run-lint }} runs-on: ubuntu-24.04 defaults: run: working-directory: ${{ inputs.working-directory }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ inputs.node-version }} cache: 'npm' cache-dependency-path: ${{ inputs.working-directory }}/package-lock.json
- run: npm ci - run: npm run lint
test: if: ${{ inputs.run-tests }} runs-on: ubuntu-24.04 outputs: passed: ${{ steps.test.outputs.passed }} defaults: run: working-directory: ${{ inputs.working-directory }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ inputs.node-version }} cache: 'npm' cache-dependency-path: ${{ inputs.working-directory }}/package-lock.json
- run: npm ci
- id: test run: | npm test echo "passed=true" >> $GITHUB_OUTPUTWorkflow appelant
Section intitulée « Workflow appelant »Côté service, le pipeline tient en quelques lignes : il appelle le template puis enchaîne un déploiement conditionnel.
name: CI
on: push: branches: [main] pull_request: branches: [main]
jobs: ci: uses: org/ci-templates/.github/workflows/node-ci.yml@v1.0.0 with: node-version: '20' run-lint: true run-tests: true secrets: inherit
deploy: needs: ci if: github.ref == 'refs/heads/main' runs-on: ubuntu-24.04 steps: - run: | echo "Tests passed: ${{ needs.ci.outputs.test-passed }}" - run: ./deploy.shPatterns avancés
Section intitulée « Patterns avancés »Une fois les bases acquises, ces patterns couvrent les cas que l'on rencontre sur de vrais portefeuilles de projets.
Workflow avec matrix
Section intitulée « Workflow avec matrix »Le workflow réutilisable peut contenir une matrix :
on: workflow_call: inputs: os: type: string default: '["ubuntu-24.04"]' node: type: string default: '["18", "20"]'
jobs: test: strategy: matrix: os: ${{ fromJSON(inputs.os) }} node: ${{ fromJSON(inputs.node) }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ matrix.node }} - run: npm ci && npm testAppel avec matrix personnalisée :
jobs: test: uses: org/templates/.github/workflows/reusable-test.yml@v1.0.0 with: os: '["ubuntu-24.04", "windows-latest"]' node: '["18", "20", "22"]'Chaîner plusieurs workflows réutilisables
Section intitulée « Chaîner plusieurs workflows réutilisables »Plusieurs workflows réutilisables s'enchaînent avec needs, exactement comme des
jobs classiques.
jobs: lint: uses: org/templates/.github/workflows/lint.yml@v1.0.0
test: needs: lint uses: org/templates/.github/workflows/test.yml@v1.0.0
build: needs: test uses: org/templates/.github/workflows/build.yml@v1.0.0 with: version: ${{ needs.test.outputs.version }}
deploy: needs: build uses: org/templates/.github/workflows/deploy.yml@v1.0.0 with: image: ${{ needs.build.outputs.image }} secrets: deploy-key: ${{ secrets.DEPLOY_KEY }}Workflow réutilisable avec permissions
Section intitulée « Workflow réutilisable avec permissions »Un workflow réutilisable déclare ses propres permissions: — le workflow
appelant doit lui accorder au moins ce niveau.
on: workflow_call: inputs: environment: type: string required: true
# Permissions déclarées dans le workflow réutilisablepermissions: contents: read id-token: write # Pour OIDC
jobs: deploy: runs-on: ubuntu-24.04 environment: ${{ inputs.environment }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Deploy run: ./deploy.shBonnes pratiques
Section intitulée « Bonnes pratiques »Quatre règles évitent les pièges les plus coûteux quand vos templates sont partagés par de nombreux dépôts.
1. Versionnez vos templates
Section intitulée « 1. Versionnez vos templates »Un workflow réutilisable est du code tiers : il s'épingle exactement comme
une action. N'appelez jamais un template en @main — une référence mobile
que le mainteneur peut faire pointer vers n'importe quoi. Utilisez un tag de
version ou, mieux, un SHA de commit :
# Acceptable : tag de versionuses: org/templates/.github/workflows/ci.yml@v1.2.0
# Le plus sûr : SHA de commituses: org/templates/.github/workflows/ci.yml@a1b2c3d4e5f6Le principe de l'épinglage et son outillage sont détaillés dans le guide Épingler les actions par SHA.
2. Documentez les inputs et outputs
Section intitulée « 2. Documentez les inputs et outputs »Une description claire sur chaque input évite aux équipes consommatrices de
devoir lire le code du template.
on: workflow_call: inputs: environment: description: | Environnement cible pour le déploiement. Valeurs acceptées : staging, production. Défaut : staging. type: string default: 'staging'3. Utilisez des valeurs par défaut sensées
Section intitulée « 3. Utilisez des valeurs par défaut sensées »Des default: bien choisis rendent le template utilisable sans configuration
dans le cas le plus courant.
inputs: node-version: type: string default: '20' # LTS actuel timeout: type: number default: 30 # Raisonnable pour la plupart des builds4. Limitez les permissions
Section intitulée « 4. Limitez les permissions »Déclarez le minimum au niveau du workflow et n'élargissez qu'au niveau du job qui en a réellement besoin.
# Dans le workflow réutilisablepermissions: contents: read # Minimum par défaut
jobs: publish: runs-on: ubuntu-24.04 permissions: contents: read packages: write # Uniquement si nécessaire steps: - run: ./publish.shLimitations
Section intitulée « Limitations »Les workflows réutilisables ont quelques contraintes structurantes — mieux vaut les connaître avant de bâtir toute une architecture dessus.
- Profondeur d'appel : les workflows réutilisables peuvent s'imbriquer, mais sur 4 niveaux maximum
- Nombre d'appels : maximum 20 workflows réutilisables uniques dans l'arbre d'appel
- Secrets : les secrets ne peuvent pas être passés dynamiquement via expressions
- Permissions : le workflow appelant doit avoir au moins les permissions requises par le workflow réutilisable
À retenir
Section intitulée « À retenir »- Un workflow réutilisable se déclare avec le trigger
workflow_callet s'appelle viauses:à la place dessteps:d'un job. - Les inputs sont typés (
string,boolean,number) ; les secrets se déclarent à part. - Les outputs remontent au workflow appelant, qui les lit via
needs.<job>.outputs. secrets: inherittransmet tous les secrets du dépôt parent — pratique, mais à éviter quand le template n'a besoin que d'un ou deux secrets.- Versionnez vos templates par tag semver ou par SHA — jamais
@mainen production. - Limites à connaître : imbrication sur 4 niveaux maximum, 20 workflows réutilisables uniques par arbre d'appel, pas de secret passé dynamiquement.
Prochaines étapes
Section intitulée « Prochaines étapes »Pour la référence complète, consultez la documentation officielle sur les workflows réutilisables.