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 fichierspackage.json
etpackage-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'utilisateurnode
, 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'environnementHOST
etPORT
à utiliser dans notre service. L'utilisation de0.0.0.0
pourHOST
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.