Le contexte est toujours le même. Ton code passe en local, tu pousses, tu mergues. Quelqu'un dans l'équipe pull la branche, lance le projet, et plante au démarrage. Ta première phrase, presque réflexe : "bizarre, chez moi ça marche".
Toi, tu sais que cette phrase est mauvaise. Elle te fait passer pour quelqu'un qui ne maîtrise pas son environnement. Elle exaspère tes collègues, qui doivent perdre une heure à comprendre pourquoi leur setup diffère du tien. Et si elle revient trois fois dans la même semaine, tu sens bien que ton crédit baisse silencieusement.
Le problème, ce n'est pas que tu es négligent. C'est que personne ne t'a jamais expliqué qu'un projet en local et un projet "qui marche partout", ce n'est pas la même chose. On t'a appris à coder, pas à rendre ton code portable. Ces compétences-là ne sont presque jamais enseignées dans les formations, alors qu'elles font la différence entre un dev qu'on garde et un dev qu'on tolère.
Cet article démonte les causes réelles du "ça marche chez moi", et te donne les six réflexes qui te permettent d'éliminer 95% des cas. Pas un cours sur Docker, pas un plaidoyer pour Kubernetes. Une méthode concrète pour des projets reproductibles dès le premier commit.
Le problème : ton "local" est une boîte noire
Quand tu codes, tu utilises ta machine. Ta machine a Node 20.5, un PostgreSQL 16 installé via Homebrew, une variable d'environnement API_KEY dans ton .zshrc, un fichier .env que tu as oublié de mentionner dans le README, une version de ImageMagick installée il y a deux ans pour un autre projet.
Quand quelqu'un d'autre clone ton repo, il a Node 18, pas de PostgreSQL, pas ta variable d'environnement, pas ton .env, et une autre version de ImageMagick. Il ne sait rien de ces différences. Tu non plus, parce que tout cela est invisible pour toi : ce sont les conditions implicites de ton setup.
Le "ça marche chez moi" n'est jamais un mensonge. C'est une vérité partielle : ça marche dans des conditions précises que personne n'a documentées. Le boulot, ce n'est pas d'apprendre à débugger plus vite après. C'est de rendre ces conditions explicites avant.
Le coût caché est lourd. Une heure perdue par chaque nouveau collègue qui clone le projet. Une heure perdue à chaque déploiement bancal. Une heure perdue quand toi-même tu changes de machine et que ton projet ne tourne plus. Sur un an, ce sont des semaines.
Le principe : un projet portable rend ses conditions explicites
Un projet portable, c'est un projet où tout ce qui doit être présent pour qu'il tourne est nommé, versionné et reproductible. Pas oralement, pas dans un Slack, pas dans la tête de l'auteur original. Dans le repo.
Il y a six couches sur lesquelles ton projet peut dépendre de ta machine. Tu les traites une par une.
1. La version du runtime
Tu codes avec Node 20, ton collègue lance avec Node 18, ça plante sur une syntaxe récente. Tu codes avec Python 3.12, lui avec 3.9, certaines libs explosent.
La solution : tu fixes la version dans un fichier dédié. Pour Node, c'est .nvmrc avec une ligne comme 20.10.0. Pour Python, c'est .python-version ou la section requires-python dans pyproject.toml. Pour PHP, composer.json a un champ "require": {"php": "^8.3"}. Pour Java, le pom.xml précise la version. Cinq secondes pour ajouter ce fichier, des heures économisées.
2. Les dépendances avec leurs versions exactes
Tu installes une lib en local, elle marche, tu commit. Six mois plus tard, ton collègue installe la même lib mais récupère une version mineure plus récente, qui a changé un comportement. Mystère, ça plante.
Le réflexe : tu commit toujours ton lockfile (package-lock.json, yarn.lock, pnpm-lock.yaml, composer.lock, poetry.lock, Pipfile.lock). Le lockfile fige les versions exactes de toutes les dépendances, y compris les sous-dépendances. Sans lockfile commit, ton projet n'est pas reproductible.
3. Les variables d'environnement
Les clés API, les chaînes de connexion, les flags. Tu les as dans ton .env local, mais le .env est (à juste titre) dans le .gitignore. Ton collègue clone, lance, plante avec un message "undefined" sur une variable qu'il n'a aucun moyen de connaître.
La solution standard : un fichier .env.example commit dans le repo, qui liste toutes les variables nécessaires avec une valeur factice ou vide. Le collègue le copie en .env, remplit ce qu'il manque, et son projet démarre. Bonus : ce fichier sert de documentation vivante des dépendances de configuration.
4. Les services externes (base, cache, queue)
Ton projet a besoin de PostgreSQL, Redis, un broker de messages, un service de mail. Tu as tout installé en local via Homebrew ou apt. Personne d'autre n'a la même chose.
La solution moderne : un docker-compose.yml qui démarre tous ces services avec une commande. Plus de "il te faut PostgreSQL 16 avec l'extension uuid-ossp", juste docker compose up et tu as tout. Si Docker te fait peur, l'article sur pourquoi Docker est devenu incontournable explique le pourquoi avant le comment.
5. Le système d'exploitation
Tu codes sur macOS, tes collègues sur Linux ou Windows. Les chemins de fichiers diffèrent (/ vs \), les fins de ligne diffèrent (LF vs CRLF), certaines commandes ne sont pas dispo partout.
Les bons réflexes : tu utilises systématiquement la lib de gestion de chemins de ton langage (path.join en Node, os.path.join ou pathlib en Python). Tu mets un .gitattributes avec * text=auto pour normaliser les fins de ligne. Et si l'équipe est mixte macOS/Windows, Docker règle 90% des problèmes restants.
6. Le README qui explique comment démarrer
Le dernier rempart. Même avec tous les fichiers ci-dessus, il faut une page qui explique l'ordre des opérations. Cloner, installer les dépendances, copier le .env, lancer Docker, faire les migrations, lancer le serveur. Cinq à dix commandes, dans l'ordre.
Un bon README de démarrage tient en une vingtaine de lignes. Si tu n'arrives pas à le faire tenir, c'est probablement que ton setup est trop compliqué.
Exemple : un projet Node + PostgreSQL portable en cinq fichiers
Voici ce que devrait contenir, au minimum, un projet Node typique pour être portable.
.nvmrc
20.10.0
.env.example
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
API_KEY=
NODE_ENV=development
PORT=3000
docker-compose.yml
services:
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
ports:
- "5432:5432"
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:
README.md (section démarrage)
## Demarrage
1. nvm use
2. npm install
3. cp .env.example .env
4. docker compose up -d
5. npm run migrate
6. npm run dev
Plus package-lock.json commit dans le repo. Total : cinq fichiers, dont aucun ne demande plus de cinq minutes à créer. Le projet est maintenant portable. N'importe qui peut le cloner et le faire tourner en moins de dix minutes sans avoir à te poser de questions.
Cette infrastructure minimale est ce qui distingue un projet professionnel d'un projet de stage. Et elle ne demande aucune compétence particulière, juste la discipline de la mettre en place dès le début.
Pièges classiques à éviter
Croire qu'un README suffit
"J'ai écrit un README détaillé, ça suffit." Non. Un README qui décrit oralement les versions à installer est moins fiable qu'un fichier .nvmrc. Le README peut devenir obsolète, le fichier de version reste à jour avec le code.
L'objectif est de minimiser la quantité d'instructions à suivre manuellement. Plus c'est automatisable, plus c'est fiable.
Ne pas tester son setup régulièrement
Ton projet marche chez toi en continu, donc tu ne testes jamais le démarrage à froid. Trois mois plus tard, quelqu'un clone, ça plante, et tu découvres une étape manquante.
Une fois par mois, tu clones ton propre projet dans un dossier temporaire et tu suis ton propre README. Tu trouveras toujours quelque chose à corriger. Si tu as la chance d'avoir une CI, elle fait ce travail pour toi à chaque commit.
Mettre des secrets dans le repo
Tu pousses ton vrai .env "juste pour cette fois", "ça simplifiera la vie des collègues". Tes clés API se retrouvent publiques. Tu passes ta soirée à les régénérer.
Le .env n'est jamais dans Git. Le .env.example oui, avec des valeurs factices. Cette règle ne souffre aucune exception.
Installer des dépendances système sans les documenter
Ton projet a besoin de libxml2, de ImageMagick, de ffmpeg, parce qu'une de tes libs en dépend. Tu l'as installé via Homebrew il y a un an, tu as oublié. Le collègue galère.
Toutes les dépendances système doivent être listées dans le README ou (mieux) inclues dans un Dockerfile. Si ton projet a besoin de plus de deux dépendances système, Docker devient la solution la plus sensée.
Croire que CI = projet portable
"Ma CI passe, donc le projet est portable." Pas forcément. Si ta CI utilise une image qui contient déjà tout ce qu'il faut, elle peut masquer des dépendances cachées. Le vrai test, c'est qu'un humain sur une machine vierge puisse lancer le projet.
La CI est un excellent compagnon de la portabilité, pas un substitut. Les deux travaillent ensemble.
Bricoler en prod plutôt qu'en local
Tu corriges un bug en prod en SSH-ant sur le serveur, tu changes une variable d'environnement à la main, tu remets en route. Ça marche le temps que ce serveur soit redémarré ou remplacé, puis le bug revient et personne ne se souvient du fix.
Tout changement passe par le code, le code passe par Git, et la prod est rebuild à partir de ce code. Sinon, la prod devient un autre "ça marche chez moi", à plus grande échelle et avec des utilisateurs réels.
Pour aller plus loin
La portabilité de projet est l'expression concrète du principe Fail Fast : tu fais échouer le démarrage explicitement quand une condition manque, plutôt que de laisser le projet planter de manière obscure plus tard. Pour aller plus loin sur la robustesse de tes projets, l'article sur les principes de single source of truth donne le cadre mental qui sous-tend tous ces réflexes.
Côté formation, la formation Docker pour développeurs web couvre la partie containerisation qui élimine 80% des sources du "ça marche chez moi". La formation CI/CD avec GitHub Actions ajoute le filet de sécurité qui détecte automatiquement les régressions de portabilité.