Aller au contenu
BackDockerNode.js

Déployez comme un pro : le guide ultime pour Dockeriser votre application Node.js !

Découvrez Docker en toute sérénité ! Grâce à ce guide étape par étape, transformez votre application Node.js en un conteneur Docker.

Photo by Kurt Cotoaga / Unsplash

Docker est un logiciel qui permet de conteneuriser des applications, c'est-à-dire d'emballer une application et ses dépendances dans un conteneur. Ce conteneur peut ensuite être exécuté de manière uniforme sur n'importe quelle machine, garantissant que l'application fonctionnera toujours de la même manière, indépendamment de l'endroit où elle est déployée.

Prérequis

Avant de plonger dans le vif du sujet, assurez-vous que Node.js et npm sont bien installés et prêts à être utilisés pour le développement de notre application. De même, vérifiez que Docker est également installé pour la conteneurisation de notre application.

Vous pouvez vérifier que tout est correctement installé en utilisant les commandes suivantes.

$ npm --version
9.5.0
$ node --version
v18.14.2
$ docker --version
Docker version 19.03.12, build 48a66213fe

Développement d'une API avec fastify

Avant de démarrer le développement avec Fastify, mettons en place l'environnement de notre projet.

$ mkdir fastify-api-project
$ cd fastify-api-project
$ npm init -y
$ npm install fastify

Avec mkdir fastify-api-project, nous créons un répertoire spécifique pour notre application. cd fastify-api-project nous permet d'entrer dans ce répertoire, c'est l'endroit où tout notre développement se déroulera. npm init -y démarre l'initialisation d'un projet Node.js, générant un fichier essentiel : package.json. Pour finir, npm install fastify installe Fastify, le framework que nous utiliserons pour développer notre API.

Étape suivante, créons le fichier index.js dans lequel nous allons ajouter ce code.

const Fastify = require('fastify');
const fastify = Fastify({ logger: true });

const PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || 'localhost';

fastify.get('/', async (request, reply) => {
    reply
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({ site: 'sfeir.dev', article: 'build node js app with docker' });
});

fastify.listen({ host: HOST, port: PORT }, (err, address) => {
    if (err) {
        console.error(err)
        process.exit(1)
    }
});

Ce code met en place un service renvoyant des données au format JSON. Notons la présence des variables d'environnement HOST et PORT, qui seront utiles au moment de la conteneurisation.

Dès que vous avez enregistré le fichier index.js, l'API est prête à être testée. En exécutant node index.js dans votre terminal, vous devriez pouvoir accéder à http://localhost:3000 dans votre navigateur et voir la réponse JSON.

Avec notre application en place, voyons comment la mettre dans un conteneur Docker.

Création de l'image Docker

Pour ce faire, nous allons créer un fichier nommé Dockerfile. Ce fichier contiendra les instructions permettant à Docker de construire une image de notre application.

FROM node:18

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY --chown=node:node . .

EXPOSE 3000

ENV HOST=0.0.0.0 PORT=3000

CMD ["node", "index.js"]

Passons en revue les instructions, ligne par ligne.

  • FROM node:18 indique à Docker de se baser sur l'image officielle de Node.js version 18. C'est la fondation sur laquelle notre conteneur sera construit. Cette version de Node.js fournira l'environnement d'exécution pour notre application.
  • WORKDIR /app définit le répertoire de travail dans le conteneur à /app. Toutes les commandes qui suivent s'exécuteront dans ce répertoire.
  • COPY package*.json ./ copie les fichiers package.json et package-lock.json (s'il existe) du répertoire source (notre machine) vers le répertoire de travail du conteneur. Elle est essentielle pour installer les dépendances nécessaires à notre application.
  • RUN npm install exécute la commande npm install dans le conteneur.
  • COPY --chown=node:node . . copie le reste des fichiers et dossiers de notre application dans le conteneur. L'option --chown=node:node garantit que les fichiers sont possédés par l'utilisateur node, augmentant ainsi la sécurité en évitant de fonctionner avec des permissions superutilisateur.
  • EXPOSE 3000 indique à Docker que notre futur conteneur écoutera sur le port 3000. C'est le port sur lequel l'API Fastify fonctionnera.
  • ENV HOST=0.0.0.0 PORT=3000 définit les variables d'environnement HOST et PORT à utiliser dans notre service. L'utilisation de 0.0.0.0 pour HOST permet à l'application d'être accessible depuis l'extérieur du conteneur.
  • CMD ["node", "index.js"] spécifie la commande qui sera exécutée lorsque le conteneur sera lancé. Ici, elle démarrera notre application avec Node.js.

La commande suivante construit une image Docker en se basant sur notre Dockerfile et lui donne le nom sfeir-dev.

docker build -t sfeir-dev .

Pour s'assurer que l'image a été correctement construite et est maintenant disponible, on peut utiliser docker images.

$ docker images
REPOSITORY                     TAG       IMAGE ID       CREATED         SIZE
sfeir-dev                      latest    d1b90f727fbc   6 seconds ago   1.1GB

Ainsi, notre image est prête à être déployée et exécutée sous forme de conteneur.

De l'image au conteneur

Nous y sommes presque. Nous allons utiliser l'image que nous venons de construire et la transformer en conteneur exécutable. Pour ce faire, nous utiliserons la commande docker run.

$ docker run -d -p 3000:3000 sfeir-dev
d3fae3a516aa6b652574b6fc726156479a1e6196234838b81cb5e467529124c0

Arrêtons nous sur cette commande :

  • docker run est la commande de base pour démarrer un conteneur à partir d'une image Docker.
  • -d est l'argument pour activer le mode détaché. Cela signifie que Docker exécutera le conteneur en arrière-plan. Sans cette option, le conteneur serait exécuté au premier plan et bloquerait le terminal.
  • -p 3000:3000 indique à Docker d'effectuer une liaison de port. Le format est <port sur l'hôte>:<port sur le conteneur>. Cela signifie que le port 3000 du conteneur sera accessible via le port 3000 de la machine locale. Dans le contexte de notre API, cela signifie que si notre application Fastify écoute sur le port 3000 à l'intérieur du conteneur, nous pouvons y accéder en utilisant le port 3000 sur notre machine.
  • sfeir-dev est le nom de l'image que nous avons créée précédemment. Docker démarrera un conteneur basé sur cette image.

Une fois que cette commande est exécutée, votre API Fastify tourne maintenant à l'intérieur d'un conteneur Docker. Vous pouvez le vérifier en accédant à http://localhost:3000 dans votre navigateur ou en utilisant un outil comme curl depuis votre terminal. Dans le même temps, Docker fournit également en sortie de la commande un identifiant unique pour ce conteneur.

Si vous n'avez pas gardé cet identifiant sous la main, pas de panique ! La commande docker ps est là pour nous aider. Elle affiche la liste des conteneurs en cours d'exécution, y compris leurs identifiants.

$ docker ps                                                                         
CONTAINER ID   IMAGE       COMMAND                  CREATED         STATUS         PORTS                    NAMES
d3fae3a516aa   sfeir-dev   "docker-entrypoint.s…"   9 seconds ago   Up 8 seconds   0.0.0.0:3000->3000/tcp   vibrant_nobel

De nombreuses commandes Docker utilisent l'identifiant comme argument afin d'interagir avec le conteneur spécifique. Par exemple, la commande docker logs permet de consulter les logs de l'application.

$ docker logs d3fae3a516aa                                                          
{"level":30,"time":1696762134174,"pid":1,"hostname":"d3fae3a516aa","msg":"Server listening at http://0.0.0.0:3000"}
{"level":30,"time":1696762807374,"pid":1,"hostname":"d3fae3a516aa","reqId":"req-1","req":{"method":"GET","url":"/","hostname":"localhost:3000","remoteAddress":"172.17.0.1","remotePort":58620},"msg":"incoming request"}
{"level":30,"time":1696762807381,"pid":1,"hostname":"d3fae3a516aa","reqId":"req-1","res":{"statusCode":200},"responseTime":5.7870680000633,"msg":"request completed"}

Suppression des ressources Docker

Pour arrêter ou supprimer le conteneur, vous aurez besoin de son identifiant. Arrêter et supprimer sont deux opérations distinctes. Arrêter un conteneur met fin à son exécution, mais il reste présent sur le système jusqu'à ce qu'il soit explicitement supprimé. Pour arrêter le conteneur, utilisez la commande docker stop. Si vous souhaitez le supprimer après l'avoir arrêté, utilisez docker rm.

$ docker stop d3fae3a516aa
d3fae3a516aa
$ docker rm d3fae3a516aa
d3fae3a516aa

Si vous décidez de supprimer l'image Docker elle-même, vous pouvez le faire en utilisant l'identifiant de l'image ou son nom. Avant cela, assurez-vous que tous les conteneurs associés à cette image soient arrêtés et supprimés. Pour supprimer l'image, utilisez la commande docker rmi.

$ docker rmi sfeir-dev
Untagged: sfeir-dev:latest
Deleted: sha256:d1b90f727fbcf466c986836ada75a3350921e11d6d82dd77b1449a8a9bc9d9d8

Larguez les amarres!

Nous avons construit notre premier conteneur Docker. Cet article s'est concentré sur l'essentiel mais Docker offre un océan de fonctionnalités et de détails qui méritent une exploration approfondie.

Pourquoi avons-nous utilisé 0.0.0.0 comme HOST ? Qu'arriverait-il si nous avions utilisé localhost à la place ?

Si vous avez déjà travaillé avec Docker, vous avez peut-être rencontré les images "alpine", reconnues pour leur légèreté. Alors, pourquoi ne pas avoir choisi node:18-alpine ?

En parlant de taille, l'instruction COPY transfère tout le contenu du répertoire dans le conteneur. Cela peut inclure des fichiers superflus ou sensibles.

Toutes ces interrogations ne sont qu'un avant-goût des nombreuses astuces et meilleures pratiques que nous pourrions explorer sur sfeir.dev, plongeant un peu plus dans l'univers de Docker.

Dernier