Volta est un utilitaire développé en rust vous permettant de gérer simplement différentes versions d’outils javascript (comme node, npm) sur un même environnement.
Un des points fort de Volta est la possibilité d’épingler dans le package.json les versions de vos outils. Votre package.json contient le bout de configuration suivant :
"volta": {
"node": "12.15.0",
"npm": "6.13.4"
}
Lorsque vous arrivez dans le dossier (ou sous dossier) contenant ce package.json, toutes les commandes node s’effectueront en version 12.15.0
et npm sera exécuté en version 6.13.4
.
Bien entendu, si vous changez de dossier, vos versions de node et npm seront alignées avec les versions décrites dans le package.json du dossier courant. Cette solution est idéale pour assurer une même version entre les environnements (dev, CI, production) sans artifice particulier.
La suite de cet article s’appuie sur cette solution d’épinglement de la version. Les autres features de Volta ne seront pas présentées et analysées ici.
A l’installation de volta, le PATH de l’utilisateur est modifié pour y ajouter un dossier bin contenant les binaires node, npm, npx, yarn.
On remarque que ces exécutables sont exactement les mêmes. Ils correspondent en effet au volta-shim qui fait proxy vers la bonne version de l’exécutable.
Prenons, par exemple, le package.json d’une SPA, on y insère les metadata pour volta nous permettant de fixer la version de node et de npm:
"name": "my-great-spa",
"scripts": {
...
},
"dependencies": {
...
},
"volta": {
"node": "16.13.1",
"npm": "6.14.8"
}
Lorsqu’on effectue la commande npm start
, le volta-shim est appelé, il analyse le package.json et forward la commande vers la bonne version de npm (la télécharge si elle n’est pas installée) :
Chez Lucca, nos backends sont implémentés en C#, nous n’utilisons pas volta pour faire tourner des programmes node en production. L’overhead apporté par le volta-shim étant très faible, je n’y vois pas de contre-indication.
À un moment de la vie de votre application, vous aurez peut-être la nécessité de Dockeriser votre application. Nous créons donc naturellement notre Dockerfile :
FROM ubuntu:focal-20211006 AS builder
ENV VOLTA_HOME /volta
ENV PATH $VOLTA_HOME/bin:$PATH
WORKDIR /app
# Installation de volta
RUN apt update && apt install -y curl
RUN curl -L https://github.com/volta-cli/volta/releases/download/v1.0.5/volta-1.0.5-linux-openssl-1.1.tar.gz \
| tar xz -C /usr/bin
RUN volta setup
## Résolution des dépendances
COPY package.json package-lock.json ./
RUN npm ci
## Génération de l'application
COPY . .
RUN npm run build -- --prod --output-path=/dist
# Création de l'image finale
FROM nginx:1.21.4-alpine
# Copie des fichiers html générés
RUN rm -rf /usr/share/nginx/html/*
COPY /dist /usr/share/nginx/html
Avec ce Dockerfile, l’objectif est atteint, la bonne version de node est utilisée et notre SPA est buildée et packagée dans l’image nginx.
En y regardant de plus près, on peut noter les faiblesses suivantes :
En 2017, Docker lance le projet open-source buildkit qui se positionne comme une réécriture du système de build de Docker permettant de prendre en compte de nombreux retours de la communauté (Si vous souhaitez en avoir plus, je vous invite à lire l’article de blog publié pour la lancement). Buildkit est nativement intégré à CLI docker depuis la version 18.09 (en version stable). Pour l’activer plusieurs choix s’offrent à vous (via variable d’environnement, en configuration le daemon docker, ou bien via la commande buildx).
Buildkit permet, entre autres, d’enrichir la syntaxe des Dockerfiles. Parmi les nouvelles possibilités, on peut noter :
<<eot
RUN --mount=type=secret
qui permet de monter des secrets sous forme de fichiers dans le Dockerfile en protégeant l’injection dans l’image finaleRUN --mount=type=cache
qui permet de créer un cache partagé entre différents builds docker. C’est cette feature que nous allons utiliser.En utilisant cette nouvelle possibilité, notre Dockerfile devient :
FROM ubuntu:focal-20211006 AS builder
ENV VOLTA_HOME /volta
ENV PATH $VOLTA_HOME/bin:$PATH
WORKDIR /app
# Installation de volta
RUN apt update && apt install -y curl
RUN curl -L https://github.com/volta-cli/volta/releases/download/v1.0.5/volta-1.0.5-linux-openssl-1.1.tar.gz \
| tar xz -C /usr/bin
RUN volta setup
## Résolution des dépendances
COPY package.json package-lock.json ./
RUN npm ci
## Génération de l'application
COPY . .
RUN npm run build -- --prod --output-path=/dist
# Création de l'image finale
FROM nginx:1.21.4-alpine
# Copie des fichiers html générés
RUN rm -rf /usr/share/nginx/html/*
COPY /dist /usr/share/nginx/html
Les seules différences avec l’ancien Dockerfile sont l’ajout de --mount=type=cache,target=/volta
sur les 3 lignes d'interaction avec npm. Cet ajout permet de dire à docker de monter le dossier /volta
depuis le dossier de cache de la machine exécutant docker :
C’est de cette manière que nous pouvons éviter de re-télécharger complétement node et npm entre nos différents builds tout en continuant à utiliser volta.
On s’aperçoit assez rapidement qu’avec cette solution le dossier
volta
n’est pas packagé avec l’image finale. Dans notre cas, cela ne nous pose pas de problème car nous utilisons uniquement volta dans une image de build intermédiaire. Si vous souhaitez utiliser volta directement avec node dans l’image finale, il vous faudra trouver une autre solution.
J’espère que cet article vous donnera envie d’aller plus loin dans la découverte de volta et buildkit. N’hésitez pas à réagir en me contactant directement sur twitter (oui, c’est le bon compte malgré sa faible activité !)