Docker est une plateforme de conteneurisation qui simplifie le conditionnement et l’exécution des applications. Les conteneurs fonctionnent comme des processus isolés avec leur propre système de fichiers, mais partagent le noyau de leur hôte. Docker s’est imposé comme un moyen de mettre en œuvre des environnements de développement reproductibles et des architectures de déploiement distribuées.

Node.js est le principal moteur d’exécution JavaScript pour le développement backend. Pour lancer avec succès un service Web Node.js, vous devez disposer d’un environnement dans lequel le moteur d’exécution est installé, du code de votre application et d’un mécanisme qui gère les redémarrages automatiques en cas de panne.

Dans ce guide, nous utiliserons Docker pour conteneuriser une application Node.js simple créée avec le populaire framework web Express. Docker est un bon moyen de déployer des systèmes basés sur Node car il produit un environnement cohérent qui inclut tout ce dont vous avez besoin pour exécuter votre service. Le démon Docker a intégré la prise en charge du redémarrage des conteneurs défaillants lorsque leur processus de premier plan se bloque, ce qui résout l’un des problèmes des déploiements Node.js.

Création de votre projet Node

Nous allons passer les détails de la mise en œuvre de votre application. Créez un répertoire pour votre projet et ajoutez-y du code serveur. Voici un app.js basique qui écoute sur le port 8080 et répond à chaque requête avec une réponse codée en dur :

const express = require(« express ») ;

const app = express() ;
app.get(« * », (req, res) => res.send(« 

It works! »)) ;
app.listen(8080, () => console.log(« Listening on 8080 »)) ;

Ajoutez Express à votre projet en utilisant npm :

npm init
npm install –save express

Démarrez votre application pour tester son fonctionnement :

node app.js

Vous devriez être en mesure de visiter localhost:8080 dans votre navigateur pour voir l’exemple de réponse.

Pour aller plus loin : Utiliser Docker et les conteneurs

Écriture d’un fichier Docker

Il est maintenant temps de commencer à Dockeriser votre projet. Tout d’abord, vous avez besoin d’une image pour votre application. Les images encapsulent votre code et vos dépendances dans un seul paquet que vous utilisez pour démarrer les instances de conteneurs. Les instructions de votre Dockerfile définissent l’état du système de fichiers initial de vos conteneurs.

Voici un Dockerfile qui fonctionne pour l’application d’exemple :

FROM node:16
WORKDIR /app

COPY package.json .
COPY package-lock.json .
RUN npm ci

COPIE app.js .
CMD [« app.js »]

Ce Dockerfile sélectionne l’image Docker officielle de Node.js comme base via l’instruction FROM. L’image hérite de tout ce qui se trouve dans la base, puis ajoute du contenu supplémentaire via les instructions suivantes.

Le répertoire de travail est défini sur /app par la ligne WORKDIR. Les instructions COPY suivantes déposeront les fichiers dans le répertoire /app à l’intérieur de l’image du conteneur.

Installation des dépendances

L’étape suivante consiste à ajouter le package.json de npm et à exécuter npm ci. Cela installera les dépendances npm de votre projet – Express dans ce cas – dans le système de fichiers du conteneur.

N’utilisez pas COPY node_modules/ . pour copier le dossier node_modules existant dans votre répertoire de projet – cela vous empêcherait de réutiliser le Dockerfile dans d’autres environnements de construction. Les Dockerfiles doivent vous permettre de créer des constructions cohérentes avec le seul contenu de votre dépôt de contrôle des sources. Si un fichier ou un dossier se trouve dans votre .gitignore, il ne doit pas être référencé dans une instruction COPY de Dockerfile.

À lire également : Stratégies de déploiements de production automatisés Docker

Copie du code de l’application

Après l’exécution de npm ci, le code de votre application est copié dans l’image. Le placement de cette instruction COPY après l’instruction RUN, la séparant des copies précédentes, est délibéré. Chaque instruction crée une nouvelle couche dans votre image ; le processus de construction de Docker met en cache chaque couche pour accélérer les constructions suivantes. Dès que le contenu d’une couche est modifié, le cache de toutes les couches suivantes est invalidé.

C’est pourquoi le code de l’application doit être copié après l’exécution de npm ci. Le code changera généralement beaucoup plus fréquemment que le contenu de votre fichier de verrouillage npm. Les reconstructions d’images qui n’impliquent que des changements de code sauteront effectivement l’étape RUN npm ci (et toutes les étapes précédentes), ce qui accélère considérablement le processus lorsque vous avez beaucoup de dépendances.

Définition de la commande de l’image

La dernière étape du Dockerfile utilise l’instruction CMD pour exécuter votre application automatiquement au démarrage du conteneur. Cela fonctionne parce que l’image de base Node.js est configurée pour utiliser le processus node comme point d’entrée. L’instruction CMD est ajoutée au point d’entrée hérité, ce qui fait que node app.js est exécuté en tant que processus de premier plan pour votre nouvelle image.

Construction de l’image

Ensuite, vous devez construire votre image :

docker build -t node-app:latest .

Docker prendra le Dockerfile dans votre répertoire de travail, exécutera les instructions qu’il contient, et marquera l’image résultante comme node-app:latest. Le point final . (point) final spécifie votre répertoire de travail comme contexte de construction de l’image. Cela détermine les chemins qui peuvent être référencés par les instructions COPY dans votre Dockerfile.

Optimisation de la construction

Une façon d’améliorer les performances de construction est d’ajouter un fichier .dockerignore à la racine de votre projet. Donnez à ce fichier le contenu suivant

node_modules/

Ce fichier définit des chemins dans votre répertoire de travail qui ne seront pas inclus dans le contexte de construction. Vous ne serez pas en mesure de les référencer dans votre Dockerfile. Dans le cas de node_modules, le contenu de ce répertoire n’est pas pertinent pour la construction puisque nous installons les dépendances à nouveau via l’instruction RUN npm ci. En excluant spécifiquement les node_modules déjà présents dans votre répertoire de travail, vous évitez de devoir copier tous ces fichiers dans l’emplacement temporaire du contexte de construction de Docker. Cela augmente l’efficacité et réduit le temps passé à préparer la construction.

Démarrage d’un conteneur

À ce stade, vous êtes prêt à exécuter votre application à l’aide de Docker :

docker run -d \
-p 8080:8080 \
–name my-app \
–restart on-failure \
node-app:latest

La commande docker run est utilisée pour démarrer une nouvelle instance de conteneur à partir d’une image spécifiée. Quelques drapeaux supplémentaires sont ajoutés afin de configurer correctement le conteneur pour le cas d’utilisation prévu :

-d - Détache votre shell du processus d'avant-plan du conteneur, l'exécutant effectivement comme un serveur d'arrière-plan.
-p - Lie le port 8080 de votre hôte au port 8080 du conteneur (sur lequel notre application d'exemple Express a été configurée pour écouter). Cela signifie que le trafic vers localhost:8080 sera transmis au port correspondant du conteneur. Vous pouvez changer le post hôte à une valeur différente en modifiant la première partie de la définition de bind, comme 8100:8080 pour accéder à votre conteneur sur localhost:8100.
--name - Attribue au conteneur un nom convivial que vous pouvez utiliser pour le référencer dans d'autres commandes Docker CLI.
--restart - Sélectionne la politique de redémarrage à appliquer au conteneur. Le paramètre on-failure signifie que Docker redémarrera automatiquement le conteneur s'il sort avec un code d'échec parce que votre application a planté.

L’image construite à l’étape précédente est référencée comme argument final de la commande docker run. L’ID du conteneur sera émis dans votre fenêtre de terminal ; vous devriez pouvoir accéder à votre application Node.js en visitant à nouveau localhost:8080. Cette fois, le serveur fonctionne à l’intérieur du conteneur Docker, au lieu d’utiliser le processus node installé sur votre hôte.

Conclusion

Docker vous aide à déployer des services web Node.js en conteneurisant l’ensemble de l’environnement de l’application. Vous pouvez démarrer un conteneur à partir de votre image avec une seule commande docker run sur n’importe quel hôte sur lequel Docker est installé. Cela supprime la complexité de la maintenance des versions de Node.js, de l’installation des modules npm et de la surveillance des situations où le processus de votre application doit être redémarré.

Lorsque vous avez apporté des modifications au code et que vous souhaitez lancer votre mise à jour, reconstruisez votre image Docker et supprimez votre ancien conteneur avec docker rm . Vous pouvez ensuite démarrer une instance de remplacement qui utilise l’image révisée.

Vous pourriez vouloir une routine légèrement différente en production. Bien que vous puissiez utiliser une installation Docker régulière avec docker run, cela a tendance à être peu maniable pour toutes les applications, sauf les plus simples. Il est plus courant d’utiliser un outil comme Docker Compose ou Kubernetes pour définir la configuration du conteneur dans un fichier qui peut être versionné dans votre référentiel.

Grâce à ces mécanismes, il n’est plus nécessaire de répéter les drapeaux d’exécution de Docker chaque fois que vous démarrez un nouveau conteneur. Ils facilitent également la réplication des conteneurs pour faire évoluer votre service et assurer la redondance. Si vous effectuez un déploiement sur un hôte distant, vous devrez également pousser votre image vers un registre Docker afin qu’elle puisse être  » tirée  » de votre machine de production.

Une autre considération spécifique à la production est la façon dont vous allez acheminer le trafic vers vos conteneurs. Les liaisons de port peuvent suffire au début, mais vous finirez par avoir besoin de plusieurs conteneurs sur un hôte, chacun écoutant sur le même port. Dans ce cas, vous pouvez déployer un proxy inverse pour acheminer le trafic vers les ports des conteneurs individuels en fonction des caractéristiques de la demande, comme le nom de domaine et les en-têtes.