OpenClaw avec Docker : installer et durcir la passerelle

OpenClaw tourne en passerelle permanente et des dizaines de milliers d'instances sont ouvertes sur Internet. Voici l'installation Docker Compose durcie, exécutée et mesurée le 7 septembre 2026.

OpenClaw avec Docker : installer et durcir la passerelle
Réponse rapide

OpenClaw est un assistant personnel open source qui tourne en passerelle permanente, écoute sur le port 18789 et exécute des skills. Sous Docker, prenez l'image officielle ghcr.io/openclaw/openclaw, publiez le port sur 127.0.0.1 uniquement, retirez toutes les capacités au conteneur et gardez le jeton dans un fichier .env hors du compose. Attention : la passerelle refuse de démarrer tant que gateway.mode n'est pas écrit, et publier sur la boucle locale ne la protège pas des autres conteneurs de la machine.

OpenClaw s’installe en une commande, et c’est exactement le problème : le 11 février 2026, SecurityScorecard comptait plus de quarante mille passerelles ouvertes sur Internet dans les vingt-quatre premières heures de son relevé, dont 35,4 % jugées vulnérables à une exécution de code à distance. Docker ne règle rien tout seul, ce qui compte, c’est ce que votre fichier compose publie, ce qu’il retire au conteneur, et où vivent les secrets.

OpenClaw, c’est quoi ?

OpenClaw est un assistant personnel open source, sous licence MIT, connu auparavant sous les noms Clawdbot puis Moltbot. Ce n’est pas un agent de code au sens de Claude Code ou de Codex, mais un gateway, un processus qui tourne en permanence, expose une interface web (la Control UI) et une API WebSocket sur le port 18789, branche des canaux de messagerie (WhatsApp, Telegram, Discord) et exécute des « skills » au nom de son propriétaire.

Le modèle de langage vient d’ailleurs : l’image officielle embarque des greffons pour Anthropic, OpenAI, xAI et Ollama, et vous fournissez la clé ou l’adresse du serveur local. La passerelle est donc une machine à exécuter des instructions, le modèle n’étant qu’un de ses fournisseurs. C’est ce qui rend son exposition réseau dangereuse, bien plus que le modèle choisi.

Pourquoi Docker plutôt qu’une installation locale ?

La documentation dit que Docker est optionnel, et elle a raison sur le plan fonctionnel. Sur le plan de la sécurité, la différence est nette. Une installation locale donne à la passerelle votre compte utilisateur : votre dossier personnel, vos clés SSH, votre trousseau, vos dépôts. Un conteneur lui donne un utilisateur sans privilège, trois volumes et rien d’autre. C’est le même raisonnement que pour les agents de code, détaillé dans cloisonner Claude Code et Codex : la seule frontière qui tienne est celle que le système d’exploitation fait respecter.

Le prix à payer est réel. Les dépendances système que réclament certains skills (ffmpeg, tmux, un navigateur) ne sont pas dans l’image, et la documentation est catégorique : installer des binaires dans un conteneur qui tourne est un piège, il faut les cuire au moment de la construction avec OPENCLAW_IMAGE_APT_PACKAGES.

Quelle image choisir ?

La documentation Docker d’OpenClaw recense deux dépôts. Le registre officiel est ghcr.io/openclaw/openclaw, avec un miroir Docker Hub sous openclaw/openclaw. Un troisième, alpine/openclaw, est un miroir non officiel que la documentation demande explicitement d’éviter, parce qu’il ne suit ni le calendrier de publication ni la politique de rétention du projet.

J’ai téléchargé les deux le 7 septembre 2026 pour vérifier l’écart.

bash
docker pull ghcr.io/openclaw/openclaw:latest   # 2 min 30 s
docker pull alpine/openclaw:latest             # 1 min 28 s

docker run --rm ghcr.io/openclaw/openclaw:latest openclaw --version
# OpenClaw 2026.9.2 (3928bad)

docker run --rm alpine/openclaw:latest openclaw --version
# OpenClaw 2026.6.9

docker image inspect ghcr.io/openclaw/openclaw:latest \
  --format '{{index .Config.Labels "org.opencontainers.image.created"}}'
# 2026-09-05T15:21:16.692Z

Le miroir alpine/openclaw était figé sur la version 2026.6.9, construite le 21 juin 2026 : près de trois mois de retard, correctifs de sécurité Debian compris. L’image officielle, elle, portait la version 2026.9.2, construite le 5 septembre, deux jours avant mon test. Le choix n’est pas une question de goût.

Prévoyez la place : docker image inspect annonce 1,10 Go de contenu pour l’image officielle, mais la colonne DISK USAGE de docker images sous Docker 29 en comptait 4,43 Go réellement occupés sur le disque.

Le fichier compose durci

La documentation fournit un docker-compose.yml complet, mais il suppose un dépôt cloné et un script d’installation. Voici la version minimale que j’ai écrite et exécutée, avec l’image pré-construite uniquement.

yaml
name: openclaw-lab

x-openclaw-env: &openclaw-env
  HOME: /home/node
  OPENCLAW_HOME: /home/node
  OPENCLAW_STATE_DIR: /home/node/.openclaw
  OPENCLAW_CONFIG_DIR: /home/node/.openclaw
  OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json
  OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
  OPENCLAW_GATEWAY_PORT: "18789"
  OPENCLAW_DISABLE_BONJOUR: "1"
  TZ: Europe/Paris

services:
  gateway:
    image: ghcr.io/openclaw/openclaw:2026.9.2
    container_name: openclaw-lab-gateway
    init: true
    restart: unless-stopped
    env_file:
      - path: .env          # le jeton vit ici, pas dans le compose
        required: true
    environment: *openclaw-env
    command: ["node", "openclaw.mjs", "gateway", "--bind", "lan", "--port", "18789"]
    ports:
      - "127.0.0.1:18789:18789"
    volumes:
      - state:/home/node/.openclaw
      - workspace:/home/node/.openclaw/workspace
      - authsecrets:/home/node/.config/openclaw
    networks: [openclaw]
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    healthcheck:
      test: ["CMD", "node", "dist/docker-healthcheck.js"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 20s

  cli:
    image: ghcr.io/openclaw/openclaw:2026.9.2
    profiles: ["cli"]
    network_mode: "service:gateway"
    init: true
    env_file:
      - path: .env
        required: true
    environment: *openclaw-env
    entrypoint: ["node", "openclaw.mjs"]
    volumes:
      - state:/home/node/.openclaw
      - workspace:/home/node/.openclaw/workspace
      - authsecrets:/home/node/.config/openclaw
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    depends_on: [gateway]

networks:
  openclaw:
    name: openclaw-lab

volumes:
  state:
  workspace:
  authsecrets:

Six décisions méritent une explication.

  • Le préfixe 127.0.0.1: dans ports : sans lui, Docker publie sur toutes les interfaces de la machine, y compris celle du réseau local. C’est la faute la plus courante, et elle explique une bonne partie des passerelles trouvées par les scanners.
  • --bind lan malgré tout : dans le conteneur, loopback voudrait dire « personne ne peut me joindre, pas même Docker ». C’est la publication du port sur 127.0.0.1 de l’hôte qui fait le travail de restriction, pas le mode de liaison interne.
  • cap_drop: [ALL] : le compose officiel ne retire que NET_RAW et NET_ADMIN. Tout retirer fonctionne aussi, vérification faite plus bas.
  • Le service cli derrière un profiles : il partage la pile réseau de la passerelle (network_mode: "service:gateway"), donc il est dans la frontière de confiance. Le profil évite qu’un docker compose up distrait ne le laisse tourner en permanence.
  • Des volumes nommés, pas des bind mounts : la documentation insiste pour monter l’état comme un répertoire, jamais comme un fichier isolé, sous peine de divergence entre l’hôte et le conteneur après une écriture de configuration.
  • env_file plutôt que environment : le jeton n’apparaît ni dans le compose, ni dans le dépôt Git.

Une précision sur le port : 18789 était déjà occupé sur ma machine par une autre passerelle, j’ai donc publié le laboratoire sur 127.0.0.1:18889. Toutes les mesures qui suivent portent sur ce port, remplacez-le par 18789 chez vous, y compris dans gateway.controlUi.allowedOrigins.

Le fichier .env se génère localement et ne se commite jamais.

bash
umask 077
printf 'OPENCLAW_GATEWAY_TOKEN=%s\n' "$(openssl rand -hex 32)" > .env
chmod 600 .env
echo '.env' >> .gitignore

Premier démarrage : la configuration manquante

Lancer la pile telle quelle ne suffit pas. Le conteneur démarre, échoue, redémarre, et recommence.

bash
docker compose up -d gateway
docker compose logs gateway | tail -3
# [gateway] loading configuration…
# [gateway] resolving authentication…
# Missing config. Run `openclaw setup` or set gateway.mode=local (or pass --allow-unconfigured).

Avec restart: unless-stopped, Docker en était à onze redémarrages avant que je regarde les journaux, et le conteneur était marqué unhealthy. La passerelle refuse de servir sans configuration explicite : c’est un bon défaut, mais il faut le savoir. La correction n’exige aucune clé de fournisseur.

bash
docker compose stop gateway

docker compose run --rm -T --no-deps --entrypoint node gateway openclaw.mjs \
  config set --batch-json '[
    {"path":"gateway.mode","value":"local"},
    {"path":"gateway.bind","value":"lan"},
    {"path":"gateway.auth.mode","value":"token"},
    {"path":"gateway.controlUi.allowedOrigins","value":["http://127.0.0.1:18789"]}
  ]'
# Updated 4 config paths. Restart the gateway to apply.

docker compose up -d --force-recreate gateway

Le --no-deps --entrypoint node n’est pas décoratif : le service cli partage la pile réseau de la passerelle et ne fonctionne donc qu’une fois le conteneur gateway créé. Pour écrire la configuration avant le premier démarrage, il faut passer par l’image de la passerelle elle-même.

Le résultat, chronométré : /healthz a répondu 200 en 7,6 secondes, et Docker a marqué le conteneur healthy à 11,9 secondes. Un redémarrage à chaud, l’état déjà initialisé, tombe à 5,0 secondes.

bash
curl -s http://127.0.0.1:18789/healthz   # {"ok":true,"status":"live"}
curl -s http://127.0.0.1:18789/startupz  # {"ok":true,"status":"started"}
curl -s http://127.0.0.1:18789/readyz    # {"ready":true}

Les journaux de démarrage sont bavards, et instructifs.

bash
[gateway] ⚠️  Gateway is binding to a non-loopback address. Ensure authentication
          is configured before exposing to public networks.
[gateway] agent model: openai/gpt-5.6-sol (thinking=medium, fast=off)
[gateway] http server listening (13 plugins: anthropic, browser, canvas,
          cua-computer, device-pair, file-transfer, geolocation, linux-node,
          memory-core, ollama, openai, talk-voice, xai; 2.7s)
[gateway] log file: /tmp/openclaw/openclaw-2026-09-07.log
[gateway] remote model catalog updated; restart the Gateway to apply it

Notez la dernière ligne : sans aucune clé configurée, la passerelle est allée chercher un catalogue de modèles sur Internet. Une passerelle « au repos » sort quand même sur le réseau.

Le fichier de configuration écrit fait 348 octets et ne contient aucun secret : le jeton reste dans la variable d’environnement.

json
{
  "gateway": {
    "mode": "local",
    "bind": "lan",
    "auth": { "mode": "token" },
    "controlUi": { "allowedOrigins": ["http://127.0.0.1:18789"] }
  },
  "meta": { "lastTouchedVersion": "2026.9.2" }
}

À côté, state/openclaw.sqlite pesait déjà 1,5 Mo. C’est là que finissent les jetons OAuth, en clair : la documentation demande de traiter ce répertoire et ses sauvegardes comme des identifiants.

Ce que le durcissement bloque vraiment

Trois vérifications valent mieux qu’une déclaration d’intention.

Les capacités et l’utilisateur

bash
docker compose exec -T gateway sh -lc \
  'grep -E "^Cap(Prm|Eff|Bnd)" /proc/1/status; id; grep NoNewPrivs /proc/self/status'
# CapPrm: 0000000000000000
# CapEff: 0000000000000000
# CapBnd: 0000000000000000
# uid=1000(node) gid=1000(node) groups=1000(node)
# NoNewPrivs: 1

Aucune capacité, utilisateur non privilégié, élévation impossible. L’image officielle fait déjà la moitié du travail : elle tourne en node (uid 1000) et lance tini comme processus 1.

Le port publié sur la boucle locale ne protège pas de Docker

C’est le résultat qui m’a le plus surpris. Le port n’est publié que sur 127.0.0.1, un conteneur quelconque, même sur un autre réseau Docker, joint pourtant la passerelle par son adresse de bridge.

bash
GWIP=$(docker inspect -f \
  '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' openclaw-lab-gateway)
echo "$GWIP"   # 172.29.0.2

# depuis le réseau par défaut, pas celui du lab
docker run --rm alpine:3.22 sh -lc \
  "apk add --no-cache curl >/dev/null; \
   curl -s -o /dev/null -w '%{http_code}\n' http://$GWIP:18789/healthz"
# 200

Le préfixe 127.0.0.1: ferme les interfaces de la machine, pas le réseau du démon Docker. Si vous hébergez d’autres conteneurs sur la même machine, un seul d’entre eux compromis suffit à parler à la passerelle. La parade est l’authentification du gateway et, sur un serveur Linux, des règles dans la chaîne DOCKER-USER : la documentation de durcissement d’OpenClaw en donne un jeu complet pour UFW, parce que les règles INPUT habituelles ne voient jamais le trafic publié par Docker.

Le réseau interne coûte plus cher qu’il n’en a l’air

Le réflexe suivant est de passer le réseau en internal: true. J’ai mesuré ce que cela retire.

yaml
# docker-compose.internal.yml
networks:
  openclaw:
    internal: true
Vérification Réseau bridge Réseau internal
fetch('https://api.anthropic.com/') depuis le conteneur HTTP 404 (joignable) fetch failed
curl http://127.0.0.1:18789/healthz depuis l’hôte HTTP 200 connexion refusée
host.docker.internal résolu oui oui
Service de l’hôte joint via host.docker.internal HTTP 200 fetch failed
Conteneur marqué healthy oui oui

Deux conséquences sont contre-intuitives. D’abord, Docker Compose ignore silencieusement la section ports sur un réseau interne : docker compose ps affiche 18789/tcp au lieu de 127.0.0.1:18789->18789/tcp, sans le moindre avertissement, et la Control UI devient inaccessible. Ensuite, host.docker.internal continue de se résoudre mais plus de router : un Ollama qui tourne sur la machine hôte n’est pas joignable non plus.

Le réseau interne n’est donc utilisable que pour une passerelle pilotée entièrement par docker compose exec, avec un modèle servi par un autre conteneur du même réseau. Pour tout le reste, la bonne réponse est un bridge dédié plus un tunnel SSH : la documentation d’OpenClaw recommande d’ailleurs Tailscale Serve plutôt qu’une liaison LAN.

Connecter un modèle sans écrire de clé dans le compose

Trois chemins existent, et aucun ne demande de coller une clé dans un fichier versionné.

  1. Un fournisseur distant : la clé va dans .env (ANTHROPIC_API_KEY, OPENAI_API_KEY), lue par env_file. Elle n’entre jamais dans openclaw.json.
  2. Un modèle local sur la machine hôte : dans un conteneur, 127.0.0.1 désigne le conteneur. La documentation impose http://host.docker.internal:11434 pour Ollama, et l’hôte doit écouter au-delà de sa boucle locale (OLLAMA_HOST=0.0.0.0:11434 ollama serve).
  3. Le binaire Claude Code dans le conteneur : possible, mais il faut persister /home/node dans un volume nommé, sinon la prochaine mise à jour d’image efface l’installation et l’authentification.

Aucune clé n’a été saisie pendant ce test, et le greffon ollama est bien chargé par la passerelle. Le chemin du modèle local se vérifie sans clé : avec un Ollama publié sur le port 11434 de l’hôte, http://host.docker.internal:11434/v1 répond 200 et au format OpenAI depuis un conteneur du réseau bridge. C’est exactement l’adresse que la documentation impose, et la raison pour laquelle 127.0.0.1 ne marcherait pas : dans un conteneur, il désigne le conteneur.

Mettre à jour l’image sans casser l’état

Les étiquettes mouvantes (latest, main, extended-stable) sont reconstruites chaque semaine à partir de la même source, pour récupérer les correctifs de sécurité du système de base entre deux versions d’OpenClaw. Chaque reconstruction publie aussi une étiquette datée immuable, du type 2026.8.1-r20260820 : c’est celle qu’il faut épingler quand vous ne voulez pas qu’un déploiement suive une étiquette mouvante.

Au changement d’image, la passerelle applique ses migrations au démarrage. Si elle n’y arrive pas, elle sort en erreur plutôt que de se déclarer saine, et avec une politique de redémarrage, vous verrez une boucle. Le remède documenté est de relancer la même image une fois avec openclaw doctor --fix sur les mêmes volumes, puis de redémarrer normalement.

bash
docker compose pull
docker run --rm -v openclaw-lab_state:/home/node/.openclaw \
  ghcr.io/openclaw/openclaw:2026.9.2 openclaw doctor --fix
docker compose up -d gateway

Choisir ses skills sur ClawHub

L’image livre 53 skills prêts à l’emploi : 51 dans le paquet de base, 2 en extra. C’est déjà beaucoup, et cela suffit pour la plupart des usages. Le reste vient de ClawHub, le registre public, c’est là que les ennuis commencent. Le format est le même SKILL.md que celui décrit dans le guide Agent Skills : un frontmatter YAML, un corps Markdown, des fichiers annexes.

La documentation des skills ne prend pas de gants : elle demande de traiter tout skill tiers comme du code non fiable et de le lire avant de l’activer. Les chiffres lui donnent raison. Un recensement publié le 1er mars 2026 attribue à Antiy CERT 1 184 skills malveillants confirmés sur ClawHub, soit environ un paquet sur cinq au pic de la campagne.

skills verify interroge le registre sans rien installer, et c’est appréciable. Son verdict global, lui, mérite d’être ouvert. Voici ce qu’il renvoie sur un skill Docker populaire, à la date du test.

bash
docker compose run --rm -T cli skills verify @ivangdavila/docker

# decision            : pass
# security.status     : clean
# security.verdict    : benign      (confidence: high)
# signature.status    : unsigned
# provenance.source   : unavailable
# signals.staticScan  : suspicious  -> suspicious.exposed_secret_literal
# signals.skillSpector: suspicious
# signals.virusTotal  : clean
# artifact.files      : 16 fichiers, dont SKILL.md (24 283 octets)

Deux des trois signaux disent « suspect », le paquet n’est pas signé, sa provenance GitHub n’est pas enregistrée, et le verdict agrégé reste « bénin, confiance élevée ». Le résumé lisible parle d’un assistant local sans exfiltration détectée, et ce n’est pas ce skill-là qui est en cause. Retenez l’écart entre les signaux et la conclusion : un score vert n’est pas une lecture.

La règle que je retiens tient en trois points : lire le SKILL.md avant d’installer (skills info donne le chemin exact du fichier), refuser tout skill qui sort sur le réseau sans que sa fonction l’exige, et préférer les 53 skills livrés avec l’image tant qu’ils suffisent. Le champ security.installPolicy de la configuration permet d’imposer ce garde-fou plutôt que de compter sur la discipline.

Ajouter des serveurs MCP

La passerelle gère ses serveurs MCP dans mcp.servers, avec une surface de commandes complète : add (qui sonde le serveur avant d’enregistrer), probe, doctor, status, tools pour filtrer les outils exposés, et login / logout pour les serveurs OAuth.

bash
docker compose run --rm -T cli mcp doctor     # défauts de configuration statiques
docker compose run --rm -T cli mcp status     # transports, sans se connecter
docker compose run --rm -T cli mcp probe      # connexion réelle, liste les capacités
docker compose run --rm -T cli mcp tools      # filtres include/exclude par serveur

Deux réflexes valent d’être gardés. mcp tools existe, servez-vous-en. Un serveur MCP expose souvent trente outils quand vous en voulez trois, et chaque outil supplémentaire est une description que le modèle lit comme une consigne. Et un serveur MCP en STDIO tourne dans le conteneur de la passerelle, donc avec ses volumes et ses variables d’environnement. La note de recherche de la Cloud Security Alliance du 4 mai 2026 recommande exactement l’inverse : un conteneur dédié par serveur, sans accès aux identifiants de l’hôte. Le sujet est traité en détail dans créer un serveur MCP en PHP.

OpenClaw, Claude Code ou Hermes Agent ?

Les trois ne jouent pas au même endroit, et les confondre mène à de mauvais arbitrages.

OpenClaw Claude Code Hermes Agent
Forme Passerelle permanente Session en terminal Agent conteneurisé
Déclencheur Messagerie, cron, Control UI Vous, au clavier Tâches et files
Surface réseau Un port ouvert en permanence Aucune écoute entrante Selon le déploiement
Modèle Fournisseur au choix, Ollama compris Anthropic Selon le déploiement

Claude Code n’écoute rien : fermez le terminal, la surface d’attaque disparaît. OpenClaw écoute en permanence, par construction, parce que c’est ce qu’on lui demande : répondre à un message Telegram à trois heures du matin. Hermes Agent, installé de la même façon dans un article dédié, occupe une troisième position : un agent pensé pour tourner en conteneur dès le départ. Le panorama des agents en ligne de commande situe les autres, et OpenCode couvre le cas de l’agent de code open source.

Tout supprimer

Un laboratoire se démonte. docker compose down seul laisse les volumes derrière lui, avec la base SQLite et ses jetons.

bash
docker compose down --volumes --remove-orphans
docker rmi ghcr.io/openclaw/openclaw:2026.9.2 alpine/openclaw:latest
rm -f .env

# vérification : rien ne doit ressortir
docker ps -a --format '{{.Names}}' | grep -i claw
docker volume ls --format '{{.Name}}' | grep -i claw
docker images --format '{{.Repository}}' | grep -i claw
docker network ls --format '{{.Name}}' | grep -i claw

Ce qu’il faut retenir

  • Prenez ghcr.io/openclaw/openclaw : le miroir alpine/openclaw avait trois mois de retard le jour du test (2026.6.9 contre 2026.9.2).
  • La passerelle refuse de démarrer sans configuration et part en boucle de redémarrage : écrivez gateway.mode=local avant le premier up.
  • Publier sur 127.0.0.1 ferme les interfaces de la machine, pas le réseau Docker : un conteneur voisin joignait la passerelle en HTTP 200.
  • internal: true supprime silencieusement le port publié et l’accès à host.docker.internal : à réserver aux passerelles pilotées par exec.
  • Le jeton vit dans .env, jamais dans le compose. state/openclaw.sqlite contient des jetons OAuth en clair et se traite comme un secret.
  • Sur ClawHub, un verdict « clean » peut recouvrir deux signaux « suspect », un paquet non signé et une provenance inconnue. Lisez le SKILL.md.

Erreurs fréquentes

Publier le port sans préfixe d'adresse ports: ["18789:18789"] publie sur toutes les interfaces, réseau local compris. Écrivez "127.0.0.1:18789:18789", et sur un serveur Linux ajoutez des règles dans la chaîne DOCKER-USER : les règles INPUT ne voient pas le trafic publié par Docker.
Croire que 127.0.0.1 isole la passerelle Un conteneur d'un autre réseau Docker a joint 172.29.0.2:18789/healthz en HTTP 200. Le préfixe ferme les interfaces de la machine, pas le démon. Comptez sur l'authentification du gateway, pas sur la publication.
Lancer docker compose up sans configuration Le conteneur boucle sur Missing config. Run openclaw setup or set gateway.mode=local et restart: unless-stopped masque l'erreur. Écrivez la configuration avec config set --batch-json et --no-deps --entrypoint node avant le premier démarrage.
Prendre alpine/openclaw pour l'image officielle C'est un miroir non officiel, figé sur 2026.6.9 du 21 juin 2026 quand l'officielle était en 2026.9.2. La documentation demande d'utiliser ghcr.io/openclaw/openclaw ou openclaw/openclaw.
Ajouter internal: true comme un simple durcissement Compose ignore alors la section ports sans avertissement, la Control UI devient injoignable, et host.docker.internal se résout mais ne route plus : un Ollama sur l'hôte devient inaccessible.

DockerMCPOllamaOpenClawSécuritéSkills

Damien Flandrin Développeur web depuis 2010, créateur de Gekkode et d’Email Impact. Chaque article est testé sur un projet réel avant publication. Contact
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.