Un nouvel ingénieur rejoint l’équipe et demande pourquoi le buildspec.yml de l’API Node principale fait 80 lignes, trois phases, deux blocs reports, une section cache et quatre entrées sous secrets-manager. La réponse honnête, c’est que chaque ligne est là parce que quelque chose s’est cassé à 2 h du matin un mardi et que le fichier en a gardé la cicatrice. Un buildspec de production n’est pas l’exemple de la documentation — c’est un artéfact stratifié. Cet article parcourt le fichier bloc par bloc.
Ce qui se passe
buildspec.yml est le contrat entre CodeBuild et votre dépôt. CodeBuild le lit au début de chaque build, résout les variables d’environnement, exécute les phases dans l’ordre et téléverse ce que le fichier déclare comme artefacts et rapports. Les phases sont le flux de travail :
install— préparation du runtime. Version de Node, gestionnaire de paquets, paquets système.pre_build— tout ce qui s’exécute avant le build proprement dit. Linting, vérification de types, résolution des secrets, login ECR.build— le build lui-même.npm run build,docker build,mvn package.post_build— ce qui se passe après un build réussi. Pousser l’image, téléverser le bundle, publier un statut sur Slack.
Chaque phase a un paramètre on-failure — ABORT (défaut, arrêter le build) ou CONTINUE (passer à la phase suivante même si celle-ci a échoué). post_build s’exécute même si build échoue lorsque on-failure: CONTINUE est défini sur build, ce qui permet de téléverser les rapports de tests d’un build raté.
Les autres blocs de premier niveau sont le contrat sur ce que CodeBuild produit :
artifacts— fichiers téléversés vers S3 (ou passés à l’étape suivante de CodePipeline).reports— résultats de tests JUnit/Cucumber/Cobertura que CodeBuild analyse et affiche dans l’interface des groupes de rapports.cache— chemins préservés entre les builds quand le cache est activé sur le projet.env— variables résolues au début du build, incluant celles venant de Parameter Store et Secrets Manager.
Comment vérifier
L’anatomie complète dans un seul fichier, pour une application Node 20 typique qui build, teste et expédie une image Docker vers ECR :
version: 0.2
env:
variables:
NODE_ENV: production
AWS_REGION: us-east-1
parameter-store:
GITHUB_NPM_TOKEN: /shared/github/npm-read-token
SENTRY_AUTH_TOKEN: /apps/web/sentry-auth-token
secrets-manager:
DATABASE_URL: prod/web/db-url:url
STRIPE_API_KEY: prod/web/stripe:secret_key
exported-variables:
- IMAGE_TAG
- COMMIT_SHA
phases:
install:
runtime-versions:
nodejs: 20
commands:
- corepack enable
- corepack prepare pnpm@9.0.0 --activate
- echo "//npm.pkg.github.com/:_authToken=${GITHUB_NPM_TOKEN}" > ~/.npmrc
pre_build:
commands:
- export COMMIT_SHA=${CODEBUILD_RESOLVED_SOURCE_VERSION:0:7}
- export IMAGE_TAG=${COMMIT_SHA}-$(date +%s)
- aws ecr get-login-password --region $AWS_REGION
| docker login --username AWS --password-stdin
$ECR_REGISTRY
- pnpm install --frozen-lockfile
- pnpm run lint
- pnpm run typecheck
build:
on-failure: CONTINUE
commands:
- pnpm run test:ci -- --reporter=junit --outputFile=reports/junit.xml
- pnpm run build
- docker build
--build-arg SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN
--build-arg COMMIT_SHA=$COMMIT_SHA
-t $ECR_REGISTRY/web:$IMAGE_TAG
-t $ECR_REGISTRY/web:latest
.
post_build:
commands:
- |
if [ "$CODEBUILD_BUILD_SUCCEEDING" = "1" ]; then
docker push $ECR_REGISTRY/web:$IMAGE_TAG
docker push $ECR_REGISTRY/web:latest
printf '{"ImageURI":"%s"}' "$ECR_REGISTRY/web:$IMAGE_TAG" > imageDetail.json
fi
artifacts:
files:
- imageDetail.json
- appspec.yml
- taskdef.json
name: web-$(date +%Y%m%d-%H%M%S)
reports:
unit-tests:
files:
- reports/junit.xml
file-format: JUNITXML
coverage:
files:
- coverage/cobertura-coverage.xml
file-format: COBERTURAXML
cache:
paths:
- /root/.pnpm-store/**/*
- node_modules/**/*
Voilà le fichier de production au complet. Maintenant la lecture bloc par bloc.
env.variables — texte clair, visible dans les logs de build. À utiliser pour les valeurs non sensibles comme la région et l’environnement Node.
env.parameter-store — résout les paramètres SSM au début du build. Chiffrés via KMS, n’apparaissent jamais dans les logs. Le rôle IAM attaché au projet a besoin de ssm:GetParameters sur chaque ARN de paramètre. Moins cher que Secrets Manager pour les jetons qui tournent rarement.
env.secrets-manager — résout les secrets de Secrets Manager, optionnellement une clé JSON spécifique avec :keyname. À utiliser pour les identifiants qui tournent (mots de passe de bases de données, clés d’API tierces avec rotation automatique). Le rôle a besoin de secretsmanager:GetSecretValue.
env.exported-variables — variables à passer à l’étape suivante de CodePipeline. Sans export, la variable est locale au build et perdue.
phases.install.runtime-versions — choisit la version installée de Node, Python, etc. dans l’image runtime. Plus rapide que d’installer la sienne.
phases.build.on-failure: CONTINUE — l’astuce clé pour la visibilité des tests ratés. Quand l’étape de tests échoue, CodeBuild marque le build comme raté mais exécute quand même post_build et téléverse le rapport JUnit. Sans CONTINUE, vous n’obtenez que le résumé d’échec ; avec, vous obtenez la vraie liste des tests qui échouent dans l’interface des rapports.
artifacts.files — fichiers téléversés vers le bucket S3 d’artefacts du projet. Dans CodePipeline, ils deviennent l’artefact d’entrée de l’étape suivante. Le fichier imageDetail.json est la convention attendue par ECS dans l’étape de déploiement.
reports — chaque rapport nommé devient un « groupe de rapports » dans CodeBuild. L’interface montre les comptes réussite/échec, les courbes de tendance et les tests les plus lents. Les rapports sont gratuits à générer et pnpm test --reporter=junit ajoute peut-être 200 ms à l’exécution.
cache.paths — chemins préservés entre les builds quand le type de cache du projet est LOCAL (gardé sur la même instance) ou S3 (téléversé après le build). Le store de pnpm à /root/.pnpm-store est la bonne cible pour pnpm ; pour npm, utilisez /root/.npm ; pour yarn berry, .yarn/cache. Mettre node_modules directement en cache est un piège — la restauration est plus lente qu’une réinstallation depuis un store chaud sur un réseau rapide.
La correction
Le modèle mental qui tient pour chaque buildspec de production qu’on écrit :
-
Un dépôt, un buildspec, plusieurs projets. Utilisez des variables dérivées de
${CODEBUILD_BUILD_ID}et un seul buildspec pour dev, staging, prod. Injectez les variables par environnement via le projet CodeBuild, pas en bifurquantbuildspec-prod.ymlvsbuildspec-staging.yml. La version qui bifurque dérive en moins d’un trimestre. -
Résolvez les secrets seulement dans
pre_build. Mettre les référencessecrets-managerdansenvest pratique mais signifie que chaque build récupère chaque secret, même les builds qui échouent àinstall. Pour les secrets par phase (par exemple le jeton Sentry utilisé seulement parbuild), tirez-les danspre_buildavecaws secretsmanager get-secret-valueetexport. La permission IAM reste étroite. -
Épinglez les versions de runtime.
nodejs: 20deviendra silencieusement 20.x.y à travers les reconstructions de l’image runtime. Pour des builds déterministes, épinglez dans le champenginesdepackage.jsonet assertez à l’installation :node --version | grep -q "^v20.11.0" || (echo "Mauvaise version de Node" && exit 1) -
Rendez
post_buildidempotent. Les builds échouent.post_builds’exécute même sur les builds ratés lorsqueon-failure: CONTINUEest défini. Protégez les pushs avec$CODEBUILD_BUILD_SUCCEEDINGpour qu’une image à moitié construite ne se retrouve jamais dans ECR. -
Utilisez
reports, pas le scraping de logs. L’amélioration de débogage la moins chère dans CodeBuild, c’est de brancher le rapporteur JUnit sur l’étape de tests et d’ajouter le blocreports. La prochaine fois qu’un test casse, vous cliquez sur le rapport et voyez exactement quel test, avec la trace. Pas de plongée dans les logs. -
Cachez le store, pas l’install. pnpm/yarn/npm installent depuis un store chaud à adressage par contenu en moins de 10 secondes. Restaurer un
node_modulesde 300 Mo depuis un cache S3 prend 30+ secondes et casse aux changements mineurs de version Node. Cachez le store.
Le buildspec grossit avec le temps. Chaque bloc a été ajouté à cause d’une vraie panne. La discipline, c’est de maintenir cette croissance principée — un dépôt, un fichier, secrets dans pre_build, rapports pour chaque étape de tests.
Stack Harbor opère CodeBuild + CodePipeline à l’échelle production dans le cadre de l’onboarding Gestion AWS et des Environnements infogérés en continu. Si votre buildspec a dérivé vers du shell copié-collé sans rapports et avec des échecs silencieux de secrets, commencez par une consultation d’infrastructure.