Dans beaucoup d'équipes, la mise en production tient à une personne. Elle connaît l'ordre des commandes, elle a les bons droits sur la console AWS, et elle sait quoi faire quand ça se passe mal. Le jour où elle est absente, on ne déploie pas, ou on déploie en croisant les doigts.
Un pipeline de mise en production sert à sortir ce savoir de la tête d'une personne pour le mettre dans le dépôt, sous une forme que toute l'équipe peut relire, lancer et, surtout, annuler.
Cet article montre une mise en place réaliste avec GitHub Actions vers AWS (exemple : une application conteneurisée sur ECS), autour de trois idées :
- Aucune clé d'accès AWS longue durée stockée dans GitHub : on passe par OIDC.
- Des contrôles explicites avant et après le déploiement.
- Un retour arrière écrit, et exécuté au moins une fois avant d'en avoir besoin.
Les exemples sont volontairement génériques : adaptez les noms, la région et les commandes à votre projet.
1. Avant le YAML : écrire le périmètre
Un pipeline sans périmètre devient vite une démo. Avant la première ligne, écrivez :
- quels dépôts et quelles branches déclenchent un déploiement ;
- quel artefact part en production (une image Docker, un bundle statique…) ;
- quels environnements AWS (compte, région) sont ciblés ;
- quels contrôles sont bloquants (tests, lint, revue, approbation manuelle) ;
- ce que « retour arrière » veut dire chez vous (redéployer la version précédente ? restaurer une base ?) et ce qui n'est pas réversible ;
- le critère de fin : pour nous, le pipeline est terminé quand un membre de l'équipe l'a relancé lui-même, pas quand la personne qui l'a écrit a réussi un déploiement.
2. Se connecter à AWS sans secret : OIDC
GitHub Actions peut obtenir un jeton OIDC signé pour chaque exécution. AWS l'échange contre des identifiants temporaires d'un rôle IAM. Plus de AWS_SECRET_ACCESS_KEY dans les secrets du dépôt, donc plus de clé à faire tourner ou à voir fuiter.
Côté AWS, une seule fois :
- Créer un fournisseur d'identité OIDC IAM pour
https://token.actions.githubusercontent.com, audiencests.amazonaws.com. - Créer deux rôles : un pour construire et pousser l'image (branche
main), un pour déployer (environnement GitHubproduction).
Politique de confiance du rôle de déploiement (remplacez 123456789012, mon-org et mon-depot) :
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:mon-org/mon-depot:environment:production"
}
}
}
]
}
Le point important est la condition sub : seul un job de ce dépôt, qui s'exécute dans l'environnement production, peut assumer ce rôle. Pour le rôle de build, la valeur devient repo:mon-org/mon-depot:ref:refs/heads/main.
Permissions à donner (principe du moindre privilège, à ajuster) :
-
rôle de build :
ecr:GetAuthorizationTokenet les actions de push sur le dépôt ECR concerné (ecr:BatchCheckLayerAvailability,ecr:InitiateLayerUpload,ecr:UploadLayerPart,ecr:CompleteLayerUpload,ecr:PutImage) ; -
rôle de déploiement :
ecs:RegisterTaskDefinition,ecs:UpdateService,ecs:DescribeServices,ecr:DescribeImages(pour le contrôle du retour arrière), etiam:PassRolelimité aux rôles de tâche et d'exécution ECS de l'application.
3. Le workflow de déploiement
Fichier .github/workflows/deploy.yml :
name: deploy-production
on:
push:
branches: [main]
permissions:
contents: read
id-token: write # nécessaire pour obtenir le jeton OIDC
concurrency:
group: deploy-production
cancel-in-progress: false # jamais deux déploiements en parallèle
env:
AWS_REGION: eu-west-3
ECR_REPOSITORY: mon-app
ECS_CLUSTER: mon-cluster
ECS_SERVICE: mon-service
CONTAINER_NAME: app
jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make lint
- run: make test
build:
needs: checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_BUILD_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- id: ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build et push de l'image (tag = SHA du commit)
env:
REGISTRY: ${{ steps.ecr.outputs.registry }}
run: |
docker build -t "$REGISTRY/$ECR_REPOSITORY:$GITHUB_SHA" .
docker push "$REGISTRY/$ECR_REPOSITORY:$GITHUB_SHA"
deploy:
needs: build
runs-on: ubuntu-latest
environment: production # règles de protection de l'environnement (approbation, etc.)
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- id: render
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: deploy/task-definition.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ vars.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
- uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render.outputs.task-definition }}
cluster: ${{ env.ECS_CLUSTER }}
service: ${{ env.ECS_SERVICE }}
wait-for-service-stability: true
- name: Smoke test
run: curl --fail --silent --show-error --retry 5 --retry-delay 10 "${{ vars.HEALTHCHECK_URL }}"
Quelques choix à expliquer à l'équipe :
- Le tag d'image est le SHA du commit. On sait exactement ce qui tourne, et revenir en arrière revient à redéployer un SHA connu. Configurez le dépôt ECR en tags immuables pour qu'un tag ne puisse pas être écrasé, et une règle de cycle de vie qui conserve assez d'images pour pouvoir revenir en arrière.
-
concurrencyempêche deux déploiements simultanés sur le même service. -
environment: productionporte les règles de protection (relecteurs obligatoires, restriction aux branches autorisées). Leur disponibilité dépend de votre offre GitHub et de la visibilité du dépôt : vérifiez la documentation GitHub pour les dépôts privés. -
wait-for-service-stabilityfait échouer le job si le service ECS ne se stabilise pas, au lieu de déclarer victoire trop tôt. -
vars.*sont des variables de dépôt ou d'environnement (ARN des rôles, registre ECR, URL de santé) : aucune n'est un secret.
4. Les contrôles : bloquants, lisibles, compris
Un contrôle utile est écrit dans le dépôt, exécuté à chaque fois, et compris par la personne qui relancera le pipeline :
- avant : build, lint, tests (et éventuellement scan de dépendances ou d'image) ;
- pendant : approbation manuelle via l'environnement si votre équipe la veut ;
- après : un smoke test sur un point de santé et, si besoin, quelques vérifications fonctionnelles rapides ;
- traçabilité : l'exécution GitHub garde le commit, l'image et l'horodatage de chaque déploiement.
Côté ECS, activez aussi le disjoncteur de déploiement (deploymentCircuitBreaker avec enable: true et rollback: true) : si les nouvelles tâches n'arrivent pas à démarrer, ECS revient automatiquement à la dernière version stable. Ce n'est pas un retour arrière fonctionnel (une version qui démarre mais se comporte mal ne sera pas détectée), c'est un filet de sécurité de plus.
5. Le retour arrière : un workflow à part, testé à froid
Le retour arrière doit être aussi simple à lancer que le déploiement. Fichier .github/workflows/rollback.yml :
name: rollback-production
on:
workflow_dispatch:
inputs:
image_tag:
description: "SHA complet du commit à redéployer"
required: true
permissions:
contents: read
id-token: write
concurrency:
group: deploy-production
cancel-in-progress: false
env:
AWS_REGION: eu-west-3
ECR_REPOSITORY: mon-app
ECS_CLUSTER: mon-cluster
ECS_SERVICE: mon-service
CONTAINER_NAME: app
jobs:
rollback:
runs-on: ubuntu-latest
environment: production
env:
IMAGE_TAG: ${{ inputs.image_tag }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.image_tag }} # définition de tâche de la même version que l'image
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Vérifier que l'image existe
run: aws ecr describe-images --repository-name "$ECR_REPOSITORY" --image-ids imageTag="$IMAGE_TAG"
- id: render
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: deploy/task-definition.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ vars.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ inputs.image_tag }}
- uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render.outputs.task-definition }}
cluster: ${{ env.ECS_CLUSTER }}
service: ${{ env.ECS_SERVICE }}
wait-for-service-stability: true
- name: Smoke test
run: curl --fail --silent --show-error --retry 5 --retry-delay 10 "${{ vars.HEALTHCHECK_URL }}"
Trois détails qui évitent les mauvaises surprises :
-
L'entrée utilisateur passe par une variable d'environnement (
IMAGE_TAG) dans les étapesrun, jamais interpolée directement dans le script : c'est la protection de base contre l'injection de commandes dans GitHub Actions. -
On récupère la définition de tâche de la même version que l'image (
ref:du checkout) : variables d'environnement et ressources restent cohérentes avec le code redéployé. - Les migrations de base de données ne se rembobinent pas toutes seules. Si une version a modifié le schéma, le retour arrière applicatif ne suffit pas. Préférez des migrations compatibles dans les deux sens (ajouter d'abord, supprimer plus tard), et écrivez noir sur blanc ce qui n'est pas réversible.
Enfin, exécutez ce workflow une fois, avec l'équipe, un jour calme. Un retour arrière qui n'a jamais tourné n'est qu'une hypothèse.
6. Et avec GitLab CI ou AWS CodePipeline ?
Le choix de l'outil dépend surtout de l'endroit où vit déjà votre code et de qui doit pouvoir approuver :
-
GitLab CI : même logique OIDC. Le job déclare un jeton avec
id_tokens(audiencests.amazonaws.com) puis appelleaws sts assume-role-with-web-identity. Dans la politique de confiance, la condition porte sur la revendicationsubau formatproject_path:mon-groupe/mon-projet:ref_type:branch:ref:main. - AWS CodePipeline : l'orchestration vit dans le compte AWS (souvent avec CodeBuild), ce qui plaît quand la contrainte est « tout dans AWS ». La source GitHub ou GitLab se branche via une connexion, et les pipelines récents proposent des mécanismes de retour arrière au niveau des étapes : vérifiez la documentation de votre version.
Aucun des trois n'est « le meilleur ». Le bon choix est celui que votre équipe saura relire et relancer.
7. Checklist de fin
- [ ] Aucun identifiant AWS longue durée dans GitHub : OIDC et rôles dédiés build/déploiement
- [ ] Conditions
subrestreintes au dépôt, à la branche ou à l'environnement - [ ] Images taguées par SHA, tags immuables, rétention suffisante pour revenir en arrière
- [ ] Contrôles bloquants avant le déploiement, smoke test après
- [ ] Un seul déploiement à la fois (
concurrency) - [ ] Workflow de retour arrière écrit et exécuté une fois
- [ ] Documentation dans le dépôt : comment déployer, comment revenir en arrière, qui approuve
- [ ] Un membre de l'équipe a relancé le pipeline sans aide
Ce dernier point est le vrai critère de réussite : le pipeline appartient à l'équipe, pas à la personne qui l'a écrit.
Chez CERVOX, c'est exactement ainsi que nous cadrons la mission « Mises en production automatisées » : pipeline dans votre dépôt, contrôles prévus, retour arrière exécuté une fois avec vous, et une fin quand votre équipe relance seule (détails sur nos missions AWS). Des questions sur l'OIDC ou le retour arrière ? Posez-les en commentaire.
Top comments (0)