Index du journalDockup / note de terrain
Note / build-fails-with-no-logs

Échec du build sans logs : comment obtenir la sortie

Un build qui échoue sans logs signifie que l'échec s'est produit avant le démarrage de votre build. Découvrez les quatre étapes concernées, comment les distinguer et comment obtenir la sortie de chacune.

« Build failed. » Aucune stack trace, aucune erreur du compilateur, absolument aucune sortie. Un build qui échoue sans logs est le message le moins exploitable qu'une plateforme puisse produire. Il signifie généralement quelque chose de précis qu'il est utile de comprendre : l'échec s'est produit avant le démarrage de l'élément qui produit les logs.

Un build ne se résume pas à une seule étape. Il en comporte quatre, et chacune échoue différemment.

Les quatre étapes

1. Récupération du code source. La plateforme clone votre dépôt sur une ref. 2. Préparation du build. Elle détermine comment effectuer le build — Dockerfile, buildpack ou framework détecté. 3. Exécution du build. Vos commandes s'exécutent. C'est la seule étape qui produit la sortie que vous attendez. 4. Packaging. Le résultat est transformé en image exécutable.

Si vous n'avez absolument aucun log, l'échec s'est produit à l'étape 1 ou 2. Votre build n'a jamais été lancé : il n'a donc rien pu afficher.

Étape 1 : votre code n'a jamais été récupéré

Les symptômes sont un silence total et un échec rapide — généralement en moins de quinze secondes.

Causes courantes, par ordre de probabilité :

  • La branche n'existe pas. Un service configuré pour déployer master alors que le dépôt a été renommé en main. L'échec est instantané et n'affiche presque rien.
  • L'accès a été révoqué. Le token ou l'installation de l'application qui fonctionnait le mois dernier a été supprimé, ou le dépôt a été déplacé vers une organisation où l'autorisation ne s'applique plus.
  • Le dépôt est privé et la connexion a expiré. Même situation que ci-dessus ; la plateforme reçoit un 404 plutôt qu'un 403, car c'est ce que renvoient les fournisseurs Git pour les dépôts privés que vous ne pouvez pas consulter.
  • Un submodule ne peut pas être récupéré. Le dépôt principal est cloné, mais un submodule utilisant une URL SSH échoue, car l'environnement de build ne possède aucune clé pour y accéder.

La vérification rapide : la plateforme affiche-t-elle un hash de commit pour le déploiement en échec ? Si ce n'est pas le cas, elle n'a jamais récupéré le code, et rien dans votre Dockerfile n'est pertinent.

Étape 2 : elle ne sait pas comment effectuer le build

Là encore, le silence est total, car aucune commande de build n'a encore été choisie.

  • Aucun Dockerfile à l'emplacement indiqué par la configuration. Un dockerfilePath pointe vers un chemin qui a été déplacé.
  • Un monorepo sans répertoire racine configuré. La plateforme examine la racine du dépôt alors que votre service se trouve dans apps/api.
  • La détection n'a rien trouvé. Aucun manifest reconnu, donc aucun buildpack ne correspond.
  • Un Dockerfile impossible à analyser. Une erreur de syntaxe à la ligne 1 provoque l'échec avant l'exécution de la moindre layer.

Étape 3 : c'est ici que les logs existent

Si vous voyez une sortie partielle qui s'arrête brutalement, vous êtes à l'étape 3. Les deux causes les plus courantes sont alors liées aux ressources plutôt qu'au code :

Mémoire insuffisante. Un build interrompu par le processus de récupération après OOM n'a pas le temps d'afficher quoi que ce soit à ce sujet. Le log s'arrête simplement au milieu d'une étape. Les builds TypeScript, webpack et Vite sur de gros codebases rencontrent régulièrement ce problème. Le signe révélateur : le même commit fonctionne sur votre ordinateur, qui dispose de plus de mémoire que le builder.

Timeout. Un build qui dépasse la limite de la plateforme est interrompu. Même symptôme : la sortie s'arrête au lieu de se terminer.

Les deux cas ressemblent à une absence de logs si l'échec survient suffisamment tôt.

Étape 4 : le build a réussi, mais le packaging échoue

Cas rare et spécifique : le build a réussi, mais l'artefact est incorrect. Il peut s'agir d'une image sans CMD ou ENTRYPOINT, d'une incompatibilité d'architecture ou d'une image trop volumineuse pour la limite de la plateforme.

L'ordre du diagnostic

# Is there a commit hash? If not, stage 1.
dockup deployments my-project/my-api --json

# Build logs of the latest deployment, streamed as it goes
dockup logs my-project/my-api --build --follow

# The full record, including which stage took how long
dockup status my-project/my-api --json

stageTimings dans cette dernière sortie est le moyen le plus rapide de localiser l'échec. Un déploiement qui a passé 0,4 seconde sur le clone avant de se terminer en échec est un échec de l'étape 1. S'il a passé quatre-vingt-dix secondes à effectuer le build avant de s'arrêter, il s'agit d'un problème de l'étape 3, très probablement lié à la mémoire.

Obtenir une sortie quand il n'y en a aucune

Trois techniques, par ordre d'effort requis :

Reproduire la contrainte en local. Il ne suffit pas de vérifier que « le build fonctionne sur ma machine » : lancez-le avec la même quantité de mémoire que celle dont dispose le builder :

docker build --memory=2g --memory-swap=2g -t test .

Si cela reproduit l'échec, vous avez trouvé la cause : il s'agit d'un problème de mémoire, et non de quelque chose de mystérieux.

Rendre votre build plus verbeux. La plupart des outils de build sont silencieux par défaut concernant le problème qui est sur le point de les arrêter.

# Print progress so a truncated log still shows where it stopped
RUN npm ci --loglevel verbose
RUN NODE_OPTIONS="--max-old-space-size=3072" npm run build

Cette ligne NODE_OPTIONS vaut la peine d'être essayée à elle seule : un build Node qui s'arrête silencieusement est très souvent limité par la taille du heap, et l'augmenter permet de corriger des builds qui ne produisaient absolument aucun diagnostic.

Effectuer une recherche dichotomique dans le Dockerfile. Commentez tout ce qui suit l'étape en échec et ajoutez des marqueurs RUN echo "reached step N". C'est rudimentaire, mais cela fonctionne quand rien d'autre ne fonctionne.

Ce qui réduit ce type de problème

Deux éléments comptent davantage que n'importe quelle technique de débogage.

Diffuser les logs plutôt que les résumer. Si la sortie n'apparaît qu'une fois le build terminé, un build interrompu ne produit rien, car le résumé est écrit à la fin. La diffusion en continu permet de conserver les logs disponibles au moment de l'arrêt, jusqu'à l'instant où il s'est produit.

dockup logs my-project/my-api --build --follow

Des étapes nommées et chronométrées. « Build failed » ne fournit qu'une information binaire. « Clone : 0,4 s, build : échec après 94 s » suffit à écarter trois des quatre causes ci-dessus sans rien avoir à lire de plus.

Foire aux questions

Pourquoi mon build ne produit-il absolument aucun log ? Parce qu'il a échoué avant l'exécution de vos commandes de build — généralement lors de la récupération du code source ou de la détermination de la méthode de build. Aucune de ces deux étapes ne produit de sortie de build.

Pourquoi le build fonctionne-t-il en local, mais pas sur la plateforme ? Le plus souvent, c'est un problème de mémoire. Votre machine en dispose davantage que le builder. Reproduisez le problème avec docker build --memory=2g pour le confirmer avant d'examiner d'autres pistes.

Que signifie un log qui s'arrête au milieu d'une étape ? Le processus a été interrompu au lieu de se terminer normalement. Les deux possibilités sont un manque de mémoire ou un timeout du build, et le processus OOM n'accorde pas au processus le temps de s'expliquer.

Ai-je besoin d'un Dockerfile ? Pas nécessairement : les plateformes peuvent détecter les types de projets courants et effectuer le build sans Dockerfile. En revanche, un échec de la détection est lui-même un échec silencieux et sans logs ; un Dockerfile explicite élimine donc toute une catégorie d'ambiguïtés.