Ce décalage revient dans presque tous les premiers déploiements que j'accompagne en formation. La cause est rarement dans le code métier. Elle est dans la manière dont l'application est reliée à la machine qui l'exécute : un chemin absolu, un mot de passe écrit en dur, une dépendance installée à la main six mois plus tôt et oubliée depuis.
Une méthode publiée en 2011 par Heroku liste douze règles pour couper ces liens un par un. Elle s'appelle The Twelve-Factor App. Cet article la traduit en langage utilisable, avec pour chaque facteur l'erreur qu'il corrige, un exemple de code, et les pièges que je vois le plus souvent chez les personnes en reconversion et les développeurs juniors.
Le problème que ces douze règles résolvent
Prends une petite API Python posée dans un dossier sur ton disque. Elle lit un fichier de données situé dans /home/adel/projets/data/clients.csv. Elle se connecte à une base de données locale avec le mot de passe root écrit ligne 12. Elle utilise une bibliothèque installée un jour avec pip install, sans trace nulle part.
Sur ta machine, tout va bien. Ces trois éléments sont des dépendances invisibles à ton environnement personnel, et le serveur n'a ni ce chemin, ni cette base, ni cette bibliothèque. L'application ne démarre pas, et le message d'erreur pointe souvent sur autre chose que la cause réelle, ce qui transforme un déploiement de quinze minutes en soirée entière passée à lire des logs.
Le même mécanisme se rejoue à chaque étape suivante : quand un collègue clone le dépôt, quand la CI lance les tests, quand tu passes de un à trois serveurs. J'ai détaillé le versant humain de cette situation dans l'article sur la phrase « ça marche sur ma machine ». Les douze facteurs en sont la traduction technique.
D'où vient cette liste
Adam Wiggins, cofondateur de Heroku, publie le document en 2011. Heroku est alors une plateforme d'hébergement qui reçoit des milliers d'applications écrites par des développeurs différents, dans des langages différents. Certaines se déploient sans accroc, d'autres cassent systématiquement. Les douze facteurs sont la synthèse de ce que les premières faisaient et que les secondes ne faisaient pas.
Le texte a quinze ans et il n'a pas pris une ride, pour une raison simple : il ne mentionne aucun outil. Ni Docker, ni Kubernetes, ni GitHub Actions, aucun de ces noms n'existait ou ne comptait à l'époque. La méthode décrit des propriétés que ton application doit avoir, pas la technologie qui te sert à les obtenir. C'est ce qui la rend encore lisible aujourd'hui alors que la moitié des tutoriels de 2020 sont périmés.
Depuis fin 2024, le manifeste est passé en open source et une réécriture communautaire est en cours dans un dépôt GitHub public, avec des issues et des pull requests comme pour du code. Une remarque personnelle : je trouve la version d'origine plus utile pédagogiquement que les variantes étendues à quinze ou vingt facteurs qu'on croise parfois. Douze règles, on les retient. Vingt, on en applique six.
Les douze facteurs, regroupés par intention
La liste officielle est numérotée de I à XII dans un ordre qui n'aide pas à la mémoriser. Voici le même contenu réorganisé en quatre familles, ce qui rend l'ensemble beaucoup plus simple à tenir en tête.
Famille 1 : le code et ce dont il a besoin
Facteur I, une base de code unique. Un dépôt Git, une application. Le même code part en développement, en préproduction et en production, seule la configuration change. Deux dépôts pour une seule application signifient qu'on maintiendra deux versions divergentes d'ici trois mois.
Facteur II, dépendances déclarées et isolées. Tout ce que ton application utilise est écrit dans un fichier versionné : requirements.txt, package.json, composer.json. Et rien ne repose sur un outil supposé présent sur le système, comme curl appelé depuis un script ou une commande convert d'ImageMagick installée localement.
Facteur V, séparer build, release et run. Trois étapes distinctes. Le build fabrique un artefact à partir du code, la release y associe la configuration de l'environnement visé, le run exécute le résultat. La conséquence directe est qu'on ne modifie jamais un fichier directement sur le serveur de production. Si tu as déjà vu quelqu'un ouvrir nano en SSH pour corriger une ligne en prod, ce facteur existe pour ça.
Famille 2 : ce qui varie d'un environnement à l'autre
Facteur III, la configuration vit dans l'environnement. Tout ce qui diffère entre ta machine et le serveur sort du code : URL de base de données, clés d'API, adresse du service mail, niveau de log. Ces valeurs arrivent par variables d'environnement. Le test proposé par la méthode est efficace : est-ce que tu peux publier ton dépôt en public à l'instant, sans fuiter un seul secret ? Si la réponse est non, le facteur III n'est pas respecté.
Facteur IV, les services externes sont des ressources attachées. Base de données, cache Redis, stockage S3, service d'envoi de mails : chacun est joignable par une URL rangée dans la configuration. Remplacer une base PostgreSQL locale par une base managée doit se faire en changeant une variable, sans toucher au code. Cette règle rejoint le principe de couplage faible détaillé dans le hub des 20 principes de code.
Famille 3 : la manière dont l'application tourne
Facteur VI, des processus sans état. Ton application ne garde rien en mémoire entre deux requêtes. Pas de session stockée dans une variable globale, pas de fichier uploadé écrit dans un dossier temporaire local, pas de compteur en RAM. Tout ce qui doit survivre part dans une base ou un cache partagé. Ce facteur est le plus contraignant des douze, et celui qui rend possibles les quatre suivants.
Facteur VII, exposition par port. L'application est autonome et écoute sur un port. Elle n'a pas besoin qu'on la dépose dans un dossier d'Apache pour exister. Elle peut ensuite être placée derrière un reverse proxy, ce qui reste un choix d'infrastructure et pas une dépendance de l'application.
Facteur VIII, concurrence par les processus. Pour encaisser plus de trafic, on lance davantage d'instances au lieu d'acheter une machine plus grosse. C'est la différence entre scale-out et scale-up. Sans le facteur VI, cette règle est inapplicable : deux instances qui gardent chacune leurs propres sessions en mémoire déconnectent l'utilisateur une requête sur deux.
Facteur IX, jetabilité. Un processus démarre en quelques secondes et s'arrête proprement quand il reçoit un signal SIGTERM : il finit la requête en cours, ferme ses connexions, puis rend la main. Une application qui met deux minutes à booter rend chaque déploiement pénible et chaque autoscaling inutile.
Famille 4 : ce qui se passe autour
Facteur X, parité entre développement et production. Même version de base de données, même version de langage, même système partout. Développer sur SQLite et déployer sur PostgreSQL est la source d'un nombre impressionnant de bugs découverts au pire moment, parce que les deux moteurs ne traitent pas les dates, les types et les contraintes de la même façon.
Facteur XI, les logs sont un flux. L'application écrit ses événements sur la sortie standard et s'arrête là. Elle ne gère ni fichiers de log, ni rotation, ni archivage. C'est l'environnement d'exécution qui collecte ce flux et l'envoie où il faut. Un print() bien placé est déjà conforme au facteur XI, ce qui en fait le plus facile à appliquer de toute la liste.
Facteur XII, les tâches d'administration sont des processus ponctuels. Migration de base, script de nettoyage, création d'un compte administrateur : ces tâches tournent dans le même environnement que l'application, avec le même code et la même configuration. Elles sont versionnées avec le reste, pas copiées à la main sur le serveur.
À quoi ça ressemble dans le code
Voici la version qui casse au déploiement, en Python :
import psycopg2
conn = psycopg2.connect(
host="localhost",
dbname="boutique",
user="root",
password="motdepasse123",
)
UPLOAD_DIR = "/home/adel/projets/boutique/uploads"
Trois facteurs sont violés ici : la configuration est dans le code (III), le service est codé en dur au lieu d'être attaché (IV), et le stockage local des fichiers empêche de lancer une deuxième instance (VI). La version conforme :
import os
import psycopg2
DATABASE_URL = os.environ["DATABASE_URL"]
S3_BUCKET = os.environ["S3_BUCKET"]
PORT = int(os.environ.get("PORT", 8000))
conn = psycopg2.connect(DATABASE_URL)
Le os.environ["DATABASE_URL"] sans valeur par défaut est délibéré : si la variable manque, l'application refuse de démarrer immédiatement plutôt que de tomber en panne à la première requête utilisateur. Le port lu depuis l'environnement avec un repli sur 8000 couvre le facteur VII, la plupart des plateformes d'hébergement imposant leur propre port.
Côté fichiers, un docker-compose.yml minimal montre la même logique appliquée à l'ensemble du projet :
services:
api:
build: .
environment:
DATABASE_URL: ${DATABASE_URL}
S3_BUCKET: ${S3_BUCKET}
ports:
- "8000:8000"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
La version de PostgreSQL est épinglée en 16 pour tenir le facteur X, et aucun secret n'apparaît dans le fichier versionné. Si cette partie te parle mais que la manipulation reste floue, c'est le contenu de la formation Docker pour développeurs web, qui reprend cette mise en conteneur pas à pas.
Les pièges les plus fréquents
Le fichier .env commité par accident. C'est l'erreur numéro un, et elle arrive à des gens expérimentés. Le fichier .env sert à charger des variables en local, il n'a rien à faire dans Git. La règle qui fonctionne : .env dans le .gitignore dès la première ligne du projet, et un .env.example versionné qui liste les clés attendues avec des valeurs bidon. Un secret poussé une fois sur GitHub doit être considéré comme compromis, même après suppression du commit, et il faut le révoquer.
Confondre configuration et paramètre métier. Le facteur III vise ce qui change d'un environnement à l'autre. Le taux de TVA, la liste des statuts de commande ou le nombre d'articles par page ne sont pas de la configuration d'environnement, ce sont des règles de ton application. Les sortir dans des variables produit un fichier de trente lignes que personne ne sait relire six mois plus tard.
Appliquer les douze facteurs à un projet qui n'en a pas besoin. Un script d'automatisation qui tourne une fois par nuit sur une seule machine ne gagne rien à être découpé en processus sans état exposant un port. Les facteurs II, III et XI restent utiles partout, les autres ont un sens quand l'application est un service qui doit rester disponible.
Croire qu'un conteneur suffit. Une application avec des mots de passe en dur reste non conforme une fois mise dans une image Docker, et l'image devient même le problème puisqu'elle transporte les secrets partout où elle est publiée. Docker facilite l'application des facteurs, il ne les applique pas à ta place.
Donner tous les droits au compte de service. La méthode d'origine ne traite pas la sécurité en profondeur, ce qui laisse souvent l'utilisateur de base de données en superutilisateur alors qu'il fait des lectures et deux écritures. Le sujet est développé dans l'article sur le principe de moindre privilège.
Repousser le facteur X à plus tard. C'est celui que je vois le plus souvent sacrifié sur les projets d'apprentissage, parce qu'installer PostgreSQL en local paraît compliqué le premier jour. Le coût arrive plus tard, sous forme de bugs qui n'existent qu'en production et qu'on ne peut pas reproduire.
Par où commencer si tu pars de zéro
Reprends un projet existant, celui que tu montres en entretien ou celui sur lequel tu travailles en ce moment, et traite les facteurs dans cet ordre : II, III, XI, puis X. Ces quatre-là demandent une soirée sur un projet de taille moyenne et éliminent la majorité des surprises au déploiement.
Le facteur VI vient ensuite, parce qu'il touche à l'architecture et demande parfois de remplacer un dossier d'uploads par un service de stockage. Les autres suivent d'eux-mêmes le jour où tu mets en place un pipeline d'intégration continue, sujet couvert par la formation CI/CD avec GitHub Actions. L'ensemble du parcours est visible dans le catalogue DevOps et déploiement.