6 min de lecture
De CodeShip à GitHub Actions : la refonte de notre pipeline CI
Face à l'arrêt de CodeShip, nous avons migré plus de 100 dépôts vers GitHub Actions. Découvrez comment nous avons profité de cette migration pour repenser notre architecture CI et standardiser nos workflows à grande échelle.

L’année dernière, nous avons reçu le genre d’annonce que toute équipe engineering redoute : CodeShip, la plateforme de CI que nous utilisions depuis des années chez Scalingo, allait mettre fin à son service, avec une échéance fixée au 31 janvier 2026.
Nous avions donc quelques mois à peine pour migrer les pipelines CI de plus d’une centaine de repos vers une nouvelle solution, sans ralentir nos équipes dev, casser les workflows existants ni perturber les déploiements en cours. Autant dire que ce n’était pas exactement ce que nous avions prévu pour terminer l’année.
En nous penchant sur le sujet, nous avons toutefois rapidement réalisé que cette contrainte pouvait aussi devenir une opportunité. Au fil des années, certaines parties de notre infrastructure CI étaient devenues difficiles à maintenir. Partager la logique entre repos s'était complexifié, tester des changements de CI en toute sécurité restait délicat, et nous avions progressivement mis en place plusieurs mécanismes destinés à contourner les limites de notre CI.
Cette migration était donc l'occasion idéale de remettre à plat notre approche de la CI et de repenser des choix qui n'avaient plus forcément de sens dans notre contexte actuel.
La CI/CD chez Scalingo
Comme beaucoup d'équipes tech, nous nous appuyons fortement sur la CI/CD pour automatiser la validation du code et déployer nos changements en toute sécurité.
Chaque changement envoyé sur nos repositories passe par une série de vérifications automatisées avant de pouvoir être déployé. Cela nous permet de détecter d’éventuelles régressions, de maintenir des workflows cohérents entre nos différents projets et de réduire la quantité de validations manuelles nécessaires.
CI/CD signifie Continuous Integration (intégration continue) et Continuous Deployment (déploiement continu).
En pratique, ce terme désigne l'ensemble des processus automatisés qui permettent de valider les changements de code, via des tests, des linters et d'autres vérifications, puis de déployer en continu les applications validées en production, de manière fiable et sécurisée.
Côté CI, nos pipelines automatisent notamment :
l'exécution des tests unitaires et d'intégration ;
l'exécution des linters ;
la vérification que les changements de code n'introduisent pas de régressions ;
l'application de différents contrôles de sécurité, notamment via l'analyse de code.
Au moment de la migration, l'ensemble de ces vérifications était exécuté par CodeShip.
Un test unitaire (unit test) est un test automatisé qui vérifie le comportement d'une portion isolée de code, généralement une fonction ou une méthode.
Un linter est un outil qui analyse automatiquement le code source afin de détecter des erreurs courantes, des problèmes de style ou certaines pratiques susceptibles de poser problème avant le déploiement du code.
Côté déploiement (CD), Scalingo s'appuie sur plusieurs mécanismes automatisés :
certains services sont déployés automatiquement via des applications Scalingo grâce à l'intégration GitHub (une fonctionnalité que nous proposons également à nos clients) ;
d'autres sont déployés directement sur nos serveurs via des outils internes basés sur Chef.
Comme CodeShip n'était responsable que de la partie CI de nos pipelines, toute la partie déploiement est restée inchangée pendant la migration.
Les limites de CodeShip pour Scalingo
CodeShip faisait partie de notre stack depuis des années et, à l'époque, ce choix avait beaucoup de sens. Aux débuts de Scalingo, une grande partie de notre stack technique et de notre culture engineering était fortement influencée par l'écosystème Ruby, au sein duquel CodeShip (auparavant connu sous le nom de Rails on Fire) était une solution de CI particulièrement populaire.
Et, pour être honnêtes, cette solution nous a bien servi pendant longtemps.
Mais à mesure que notre plateforme grandissait, certaines limites sont devenues de plus en plus difficiles à contourner au quotidien.
Le principal point de friction concernait le partage de logique CI entre repositories.
CodeShip ne proposait pas de mécanisme simple pour mutualiser les scripts utilisés pour exécuter les tests unitaires ou les linters entre plusieurs projets. Avec plusieurs centaines de repos, dupliquer ces scripts de CI dépôt par dépôt était devenu particulièrement difficile à maintenir.
Pour contourner cette limitation, nous avions mis en place notre propre mécanisme :
les scripts d'exécution de la CI étaient stockés dans un object storage ;
les repositories les téléchargeaient dynamiquement au moment de l'exécution des pipelines.
Pendant un temps, cette approche a plutôt bien fonctionné. Elle nous permettait de centraliser la logique CI sans avoir à modifier chaque repository individuellement.
Mais avec le temps, cette solution de "contournement" a fini par créer ses propres problèmes opérationnels :
les mises à jour des scripts devaient être réalisées manuellement ;
la gestion des permissions était chronophage ;
certains uploads pouvaient occasionnellement être oubliés ;
et toute modification impactait immédiatement l'ensemble des repositories utilisant ces scripts partagés.
Tester des changements de CI en toute sécurité est également devenu plus compliqué. Une erreur dans un script partagé pouvait casser les pipelines de dizaines de repos en quelques minutes.
Petit à petit, cette architecture est devenue fragile, difficile à faire évoluer sereinement et de plus en plus coûteuse à maintenir au quotidien.
Pourquoi nous avons choisi GitHub Actions
Lorsque CodeShip a annoncé sa fermeture, l'entreprise a proposé un chemin de migration vers CloudBees, l'un des autres produits de sa maison mère. Mais puisque nous devions de toute façon migrer, nous avons préféré saisir cette occasion pour prendre du recul et repenser en profondeur la manière dont nous voulions structurer notre CI à l'échelle de l'entreprise.
Après avoir étudié plusieurs alternatives, nous avons finalement choisi GitHub Actions, qui nous semblait être la solution la plus adaptée pour plusieurs raisons :
Une intégration native à GitHub
L'un des avantages de GitHub Actions était évident : l'ensemble de nos repositories étaient déjà hébergés sur GitHub, ce qui permettait aux workflows de s'intégrer directement avec :
les pull requests ;
les pushs ;
les tags ;
les permissions ;
et les paramètres des repositories.
GitHub Actions étant directement intégré à GitHub, l'expérience développeur nous a également semblé beaucoup plus fluide et cohérente qu'avec une plateforme de CI externe.
Des workflows CI réutilisables et plus sûrs
L'une des améliorations les plus importantes pour nous concernait la gestion des workflows réutilisables et de la logique CI partagée.
GitHub Actions facilite la création d'actions et de workflows réutilisables directement intégrés à la plateforme.
Au lieu de dupliquer des scripts entre repositories ou de nous appuyer sur nos mécanismes maison basés sur l'object storage, nous pouvions :
centraliser la logique des workflows ;
composer des workflows à partir d'actions réutilisables ;
et tester progressivement les changements apportés à notre CI avant de les déployer plus largement.
Avec notre ancienne architecture, une modification d'un script CI partagé pouvait impacter instantanément l'ensemble des repositories qui l'utilisaient. Avec GitHub Actions, les workflows et les actions sont versionnés et peuvent être associés à différentes branches, ce qui permet de tester les changements en toute sécurité sur des repositories isolés avant un déploiement plus large.
Ce modèle basé sur le versionnement et les branches a également grandement facilité la migration elle-même. Plutôt que de mettre à jour tous les repositories en une seule fois, nous avons pu migrer et valider progressivement les workflows et les actions réutilisables, repository par repository.
Un écosystème riche
L'écosystème constituait également un atout important.
GitHub Actions permet de composer des workflows à partir d'actions publiées par la communauté ou directement par les éditeurs de logiciels. Bon nombre des outils que nous utilisions déjà proposaient des intégrations GitHub Actions officielles, ce qui les rendait particulièrement simples à réutiliser dans nos workflows.
Nous pouvions ainsi construire nos workflows à partir de briques existantes plutôt que de devoir maintenir en interne des scripts spécifiques pour chaque besoin.
Au final, GitHub Actions n'a pas seulement remplacé CodeShip. Cette migration nous a permis d'adopter une approche beaucoup plus simple et plus robuste pour mutualiser, composer et faire évoluer nos workflows CI à l'échelle de nos repositories.
Comment fonctionne GitHub Actions ?
GitHub Actions est la plateforme d'automatisation native de GitHub pour les workflows CI/CD.
Au cœur du système se trouvent les workflows : des fichiers de configuration YAML stockés directement dans les repositories, sous le répertoire : .github.workflows/
Chaque workflow définit un processus automatisé qui exécute un ou plusieurs jobs.
Les workflows sont pilotés par des événements (events). Ils sont déclenchés automatiquement lorsqu'un event spécifique se produit sur un repository, par exemple :
l'ouverture d'une pull request ;
un push de code ;
la création d'un tag Git ;
ou la publication d'une release.
Un workflow est ensuite composé de plusieurs briques :
les jobs, qui définissent des groupes de tâches à exécuter ;
les steps, qui correspondent aux différentes étapes d'exécution d'un job ;
et les actions, qui représentent des éléments de logique réutilisables pouvant être partagés entre plusieurs workflows.
Un "step" peut soit :
exécuter un script shell ;
soit appeler une autre GitHub Action.
L'ensemble s'exécute sur ce que GitHub appelle un runner, c'est-à-dire un serveur chargé d'exécuter le workflow. Aujourd'hui, nous utilisons les runners hébergés par GitHub.
L'exemple ci-dessous montre un workflow très simple qui exécute des tests unitaires Go sur chaque pull request.
Centraliser la logique CI chez Scalingo
Afin d'éviter de dupliquer la logique CI entre nos repositories, nous maintenons un repository dédié qui regroupe nos GitHub Actions réutilisables : github.com/scalingo/actions
Ce repository contient les actions partagées utilisées par plusieurs technologies et projets chez Scalingo, notamment Go, Ruby, JavaScript et Lua.

Les repos conservent ainsi des fichiers de workflow relativement légers et délèguent l'exécution à des actions réutilisables partagées.
Par exemple, un workflow peut simplement référencer une action comme :
ou :
Un exemple de workflow Go
Go est l'un des langages les plus utilisés chez Scalingo, et l'ensemble de nos repositories Go s'appuient sur une structure de workflow CI commune.
Le workflow lui-même reste relativement concis.
Il définit :
les événements qui déclenchent la CI ;
les permissions du repository ;
ainsi que deux jobs distincts :
un job de linting ;
et un job de tests.
Le workflow est déclenché :
à l'ouverture d'une pull request ;
ainsi que lors d'un push sur les branches principales utilisées pour la production.
Le job de linting n'est exécuté que sur les pull requests, tandis que les tests unitaires sont également lancés lors des pushs sur les branches principales.
Le workflow définit également explicitement les permissions du repository. Dans cet exemple, seul un accès en lecture est accordé, les jobs n'ayant besoin d'accéder qu'au code source du repository.
Plutôt que d'intégrer directement des scripts shell dans le workflow, les jobs délèguent leur exécution à des actions réutilisables stockées dans le repository Scalingo/actions.
L'un des aspects les plus intéressants de ce modèle est que les actions sont versionnées par branche.
Le suffixe @main indique à GitHub Actions d'utiliser la version de l'action présente sur la branche main. Pendant le développement, un workflow peut toutefois référencer temporairement une autre branche.
Cela permet de tester des modifications de CI en toute sécurité sur des repositories isolés avant de les déployer plus largement, ce qui était beaucoup plus difficile avec notre ancienne architecture basée sur CodeShip.
Le workflow lui-même reste relativement compact, car l'essentiel de la logique CI est implémenté dans des actions réutilisables partagées. Pour les projets Go, l'une des plus importantes est go-test, qui prend en charge la majeure partie de la préparation et de l'orchestration nécessaires avant l'exécution des tests unitaires.
Zoom sur l'action go-test
L'action go-test est implémentée sous la forme d'une GitHub Action composite.
Plutôt que de simplement exécuter un go test, elle orchestre automatiquement plusieurs étapes de préparation et de validation.
Le workflow :
récupère le code source du repository ;
installe la toolchain Go ;
détermine quels services externes sont nécessaires ;
démarre ces services de manière conditionnelle ;
exécute d'éventuels hooks d'initialisation spécifiques au repository ;
puis lance les tests unitaires Go.
L'action commence par appeler l'action officielle actions/checkout de GitHub, qui clone le code du repository sur le runner chargé d'exécuter le workflow.
Elle installe ensuite les dépendances nécessaires à la compilation et à l'exécution des tests des applications Go.
L'un des aspects les plus intéressants concerne la gestion automatique des services externes. Certains projets Go chez Scalingo ont besoin de services tels que MongoDB, etcd ou Redis pendant l'exécution des tests.
Pour répondre à ce besoin, l'action comprend des scripts de détection qui analysent les dépendances du projet afin de déterminer quels services doivent être démarrés.
Par exemple, si une dépendance MongoDB est détectée dans un projet Go, le workflow démarre automatiquement une instance MongoDB avant d'exécuter les tests.
Ce comportement peut toutefois être surchargé manuellement via les paramètres du workflow lorsque cela est nécessaire.
Avant d'exécuter les tests, l'action vérifie également la présence d'un script d'initialisation optionnel propre au repository :
Si ce script est présent dans le repository testé, il est exécuté automatiquement avant le lancement des tests. Cela permet à certains projets d'adapter le comportement de la CI à des besoins spécifiques sans avoir à modifier l'action partagée elle-même.
Enfin, le workflow exécute les tests unitaires Go via :
avec des paramètres supplémentaires propres à l'environnement interne de Scalingo.
Standardiser la CI pour tous nos repositories
Même si les détails d'implémentation varient légèrement selon les technologies, cette approche est également utilisée pour nos projets JavaScript, Ruby et Lua.
Avec le recul, cela a été l'un des bénéfices les plus importants de cette migration.
Au lieu de maintenir de grandes quantités de logique CI dupliquée repository par repository, nous disposons désormais d'une base commune d'actions réutilisables que nous pouvons faire évoluer progressivement au fil du temps.
Les repositories restent relativement légers, le comportement de la CI est plus cohérent entre les projets, et les changements apportés aux workflows peuvent être testés beaucoup plus sereinement avant un déploiement plus large.
Au final, cette migration n'avait pas seulement pour objectif de remplacer CodeShip. Elle a également été l'occasion de repenser la manière dont les workflows CI sont partagés, maintenus et mises à jour à l'échelle de Scalingo.

Étienne Michon
Docteur en informatique, Étienne Michon occupe actuellement le poste d'ingénieur R&D chez Scalingo. Il était l'un des premiers employés de Scalingo et il contribue grandement à faire grandir ce blog grâce à ses articles techniques de qualité.
Restez informé
Recevez des articles et des mises à jour de la plateforme dans votre boîte de réception.
Prêt à déployer en toute confiance ?
Découvrez des déploiements sans temps d'arrêt, une mise à l'échelle automatique intelligente et une infrastructure entièrement gérée. Commencez à déployer vos applications sur Scalingo dès aujourd'hui.
Aucune carte de crédit requise • Déployez en quelques minutes • Annulez à tout moment





