📦 Boilerplate : Docker & Node.js
Si vous développez avec Node.js sous Docker sur un système Linux (Debian, Ubuntu, etc.), vous avez forcément déjà rencontré ce scénario agaçant : vous lancez un conteneur pour générer un projet ou installer une dépendance, et soudain, les fichiers sur votre machine hôte appartiennent à root. Résultat ? Votre IDE ne peut plus les modifier, et vous passez votre temps à taper des sudo chown.
Dans cet article, je vous partage la configuration que j'utilise dans mon boilerplate Node.js pour aligner les utilisateurs de l'hĂ´te et du conteneur.
Le Problème : Le choc des UID
Par défaut, de nombreux conteneurs Docker s'exécutent en tant qu'utilisateur root (UID 0). Lorsque Docker monte un volume (votre code source), tout fichier créé par le conteneur l'est avec l'UID du processus interne.
Sur Linux, contrairement à macOS ou Windows qui utilisent des couches de virtualisation transparentes pour les permissions, l'UID est partagé directement. Si le conteneur écrit un fichier avec l'UID 1000 et que votre utilisateur local est aussi 1000, tout va bien. Mais si le conteneur utilise root (0) ou un UID node par défaut (souvent 1000) qui ne correspond pas au vôtre, c'est le conflit assuré.
La Solution : Synchroniser les UID/GID au build
L'idée est simple : récupérer l'identifiant de l'utilisateur qui lance le build (vous) et modifier l'utilisateur node interne à l'image Alpine pour qu'il utilise les mêmes identifiants.
1. Le Dockerfile.dev
Nous utilisons l'image node:lts-alpine. Pour modifier l'utilisateur existant de manière propre, nous installons temporairement l'outil shadow.
# [PROJECT_ROOT_PATH]/docker/app/Dockerfile.dev
FROM node:lts-alpine3.22
# On définit des arguments avec des valeurs par défaut
ARG UID=1000
ARG GID=1000
# 1. On installe 'shadow' pour avoir usermod et groupmod
# 2. On modifie l'utilisateur 'node' déjà présent dans l'image
# 3. On ajuste les permissions du dossier home
RUN apk add --no-cache shadow \
&& groupmod -g "${GID}" node \
&& usermod -u "${UID}" -g "${GID}" node \
&& chown -R node:node /home/node
# On repasse sur l'utilisateur node (sécurité et cohérence)
USER node
WORKDIR /home/node
EXPOSE 3000
2. Le Docker Compose
Pour injecter vos identifiants personnels, on utilise les variables d'environnement dans le fichier docker-compose.yml.
# [PROJECT_ROOT_PATH]/docker-compose.yml
services:
# Next.js application
app:
build:
context: ./docker/app
dockerfile: Dockerfile.${ENVIRONMENT}
args:
UID: ${UID}
GID: ${GID}
container_name: app
hostname: app
tty: true
stdin_open: true
user: "node"
working_dir: "/home/node"
env_file:
- .env
ports:
- "3000:3000"
volumes:
- "./sources/app:/home/node"
networks:
- boilerplate-network
networks:
boilerplate-network:
name: boilerplate-network-${ENVIRONMENT}
driver: bridge
3. Le fichier .env
Avant de lancer votre projet, vous renseignez vos propres identifiants (que vous pouvez trouver en tapant id -u et id -g dans votre terminal).
ENVIRONMENT=dev
UID=1000
GID=1000
Pourquoi j'aime bien cette approche ?
1. Transparence totale
Quand vous exécutez un npm install ou un npx create-next-app à l'intérieur du conteneur, les fichiers générés dans votre dossier sources/ vous appartiennent instantanément sur votre machine Linux. Plus besoin de chown.
2. Sécurité (Principe du moindre privilège)
En utilisant l'instruction USER node, nous ne lançons jamais l'application en tant que root. C'est une excellente pratique de sécurité, même en développement, pour se rapprocher des conditions de production.
3. Flexibilité
Si vous travaillez en équipe, chaque développeur peut avoir un UID différent (par exemple 1001). Il lui suffit de modifier son fichier .env local, et Docker reconstruira l'image adaptée à son système sans modifier une seule ligne de code du projet.
Conclusion
Pour moi, l'alignement des UID/GID via les build args est la méthode la plus simple pour éviter les problèmes en développement sous Linux. C'est un petit investissement de configuration au début du projet qui sauve des heures de frustration sur le long terme.