D2 : conteneurs imbriqués et moteurs de mise en page
Écrire en D2 les vues contexte, conteneurs et déploiement d'une boutique, choisir entre dagre, ELK et TALA, formater, exporter et rendre en CI avec D2 0.9.0.
L’issue s’ouvre déjà remplie avec le titre et l’adresse de cette page.
D2 est un langage de script de diagrammes (« a modern diagram scripting language
that turns text to diagrams », sous licence MPL-2.0) livré en un seul binaire,
sans Node ni Java. C'est l'outil le plus à l'aise avec les schémas à
groupes imbriqués : une boîte qui en contient d'autres, comme le système
d'une vue conteneurs C4 ou les nœuds d'un déploiement. Cette leçon s'adresse à
quiconque doit dessiner une architecture dans un dépôt. Vous allez installer D2
0.9.0, écrire trois vues de la boutique en ligne, comparer ses trois moteurs
de mise en page (les programmes qui calculent l'emplacement des boîtes),
puis l'automatiser.
Installer D2 en vérifiant la somme de l'archive, sans curl | sh.
Écrire une vue contexte, une vue conteneurs et un déploiement imbriqué.
Choisir le moteur de mise en page adapté à la forme du schéma.
Formater, valider et exporter un schéma en SVG, PNG, PDF ou texte.
Repérer l'erreur que D2 ne signale pas, et rendre le tout en CI.
Le principe du diagramme écrit en texte, ses avantages en revue de code et les trois chemins de rendu
(forge, ligne de commande, Kroki) sont posés dans la leçon d'entrée
Diagrammes as code : cette leçon
se concentre sur D2. Le modèle visé est le
modèle C4, notamment le
diagramme de conteneurs. Les leçons
Mermaid et
PlantUML traitent les deux autres outils.
Comment installer D2 sans exécuter un script inconnu ?
Le site de D2 recommande curl -fsSL https://d2lang.com/install.sh | sh -s --.
Ce geste est écarté ici : il exécute immédiatement un script qu'on n'a pas
lu, avec vos droits. La méthode retenue télécharge l'archive de la release
(le binaire, c'est-à-dire le programme déjà compilé, y est empaqueté) et son
fichier de sommes SHA256. Une somme SHA-256 est l'empreinte du fichier :
si un seul octet change, elle change, donc on vérifie avant de lancer quoi que
ce soit. La méthode a été jouée sous Linux x86_64 avec la version 0.9.0,
publiée le 7 septembre 2026.
La vérification doit afficher d2-v0.9.0-linux-amd64.tar.gz: OK (la somme
attendue commence par 5669ddc4 et finit par fdd87a9), puis la dernière
commande répond v0.9.0. L'archive contient aussi un Makefile : make -sC d2-v0.9.0 install place D2 dans votre dossier utilisateur. La documentation
mentionne enfin gh attestation verify avec --repo d2lang/d2 pour vérifier la
provenance de l'archive ; cette vérification n'a pas été rejouée ici.
Trois alternatives existent : brew install d2 (Homebrew), go install github.com/d2lang/d2@latest (avec Go récent) ou l'image Dockerd2lang/d2:v0.9.0@sha256:0f9237e0213338415de5ef7436c92450ddd04e0e6e7df2db3f41387322df97b9.
Le digest après @sha256: est l'empreinte de l'image : il la fige, là où un
tag peut être redéplacé. terrastruct/d2 est un miroir de même digest : le
dépôt a migré de terrastruct/d2 vers d2lang/d2 à la version 0.8.0. Avec
l'image, le dossier courant se monte dans /home/debian/src :
Une source D2 se lit presque comme une liste de phrases. La vue contexte de
la boutique montre le système au centre, ses deux personnes et ses deux
services externes. Avant de lire le code, repérez trois constructions :
id: Libellé déclare un nœud (l'identifiant sert aux liaisons, le libellé
est ce que le lecteur voit), {shape: person} change sa forme, et
a -> b: libellé trace une liaison étiquetée. direction: right oriente le
dessin de gauche à droite.
direction: right
client: Client {shape: person}
gestionnaire: Gestionnaire de boutique {shape: person}
boutique: Boutique en ligne
paiement: Service de paiement (externe)
mail: Service de mail (externe)
client -> boutique: Consulte le catalogue et commande (HTTPS)
gestionnaire -> boutique: Gère le catalogue et les stocks (HTTPS)
boutique -> paiement: Débite la carte (JSON/HTTPS)
boutique -> mail: Envoie les e-mails de commande (SMTP)
Le rendu se lance par une seule commande, d2 contexte.d2 contexte.svg. D2
répond success: successfully compiled contexte.d2 to contexte.svg in 6.39466ms (la durée varie). Dans un libellé, \n force un retour à la ligne,
ce que la vue suivante utilise pour séparer le nom de la technologie.
C'est le point fort de D2. Un nœud qui reçoit des accolades devient un conteneur :
les nœuds déclarés à l'intérieur lui appartiennent, et leur identifiant complet
s'écrit avec un point. La vue conteneurs C4 s'écrit donc naturellement :
boutique contient web, api et base, les liaisons internes s'écrivent
dans les accolades, et les liaisons qui arrivent de l'extérieur citent le
cheminboutique.web. Remarquez aussi shape: cylinder, la forme
classique d'une base de données.
direction: right
client: Client {shape: person}
gestionnaire: Gestionnaire de boutique {shape: person}
boutique: Boutique en ligne {
web: Interface web\n(JavaScript, navigateur)
api: API\n(Node.js)
base: Base de données\n(PostgreSQL) {shape: cylinder}
web -> api: Appelle (JSON/HTTPS)
api -> base: Lit et écrit (SQL/TCP)
}
paiement: Service de paiement (externe)
mail: Service de mail (externe)
client -> boutique.web: Consulte le catalogue et commande (HTTPS)
gestionnaire -> boutique.web: Gère le catalogue et les stocks (HTTPS)
boutique.api -> paiement: Débite la carte (JSON/HTTPS)
boutique.api -> mail: Envoie les e-mails de commande (SMTP)
Depuis l'intérieur d'un conteneur, la documentation de D2 désigne le parent par
le caractère _, utile pour relier un enfant à un nœud extérieur sans citer
tout le chemin ; nous n'en avons pas besoin ici. Le schéma suivant est le rendu
de cette source (légèrement habillée par la charte du site) : regardez la
Boutique comme une seule boîte qui enferme les trois conteneurs, avec l'API
mise en avant et les services externes atténués.
Rendu D2 de la source ci-dessus : le conteneur imbriqué Boutique en ligne, l'API mise en avant, les services externes atténués.Source · SVG
Les schémas du site sont rendus par la version 0.7.1 de D2, celle du
poste de publication ; les images de cette leçon qui précisent « D2 0.9.0 »
viennent, elles, de la version enseignée.
Comment décrire un déploiement sur trois niveaux ?
La vue déploiement, présentée dans les
vues complémentaires C4, dit où
tourne chaque conteneur. En D2, elle imbrique les nœuds plus profondément : une
machine contient un environnement d'exécution, qui contient le conteneur C4
déployé. Chaque
niveau s'ouvre par ses accolades, et la liaison cite le chemin complet,
séparé par des points, de la boîte la plus extérieure jusqu'à la plus
intérieure. Les services externes restent à la racine.
prod.app.node.api -> prod.donnees.pg.base: Lit et écrit (SQL/TCP)
prod.app.node.api -> paiement: Débite la carte (JSON/HTTPS)
prod.app.node.api -> mail: Envoie les e-mails (SMTP)
Dans le schéma, suivez le trajet d'une requête : elle part du navigateur du
client, entre dans la zone Production par le serveur applicatif, atteint l'API
dans Node.js, puis la base de données hébergée par PostgreSQL. Le libellé
« x2 » signale que le serveur applicatif existe en deux exemplaires.
Déploiement de la boutique en D2 : le navigateur du poste client appelle l'API, qui tourne dans Node.js 22 sur le serveur applicatif Ubuntu 24.04 en deux exemplaires, dans la zone Production ; l'API lit et écrit dans la base PostgreSQL 17 du serveur de données Ubuntu 24.04 ; elle débite la carte auprès du service de paiement externe et envoie les e-mails au service de mail externe.Poste du clientProductionService de paiement(externe)Service de mail(externe)Navigateur webServeur applicatif(Ubuntu 24.04, x2)Serveur de données(Ubuntu 24.04)Interface webNode.js 22PostgreSQL 17APIBase de donnéesAppelle (JSON/HTTPS)Lit et écrit (SQL/TCP)Débite la carte (JSON/HTTPS)Envoie les e-mails (SMTP)
D2 décrit quoi relier, jamais où placer les boîtes : c'est un moteur
de mise en page qui décide. La commande d2 layout liste ceux de la version
0.9.0 : dagre (bundled), elk (bundled) et tala (bundled). On en change
avec l'option --layout=elk ou la variable d'environnement D2_LAYOUT=elk.
Les trois ont des caractères différents. dagre produit des mises en page
hiérarchiques « based on Graphviz's DOT algorithm » (selon la documentation de
D2) et c'est le moteur par défaut. ELK est aussi un moteur de graphes
orientés ; la documentation de D2 le dit « more mature than dagre, better
maintained » et précise que width et height d'un conteneur sont gérés par
TALA et ELK. TALA
est le moteur maison de Terrastruct, conçu « for software architecture
diagrams » ; d2 layout tala montre l'option --tala-seeds (valeur par défaut
[1,2,3]) : D2 essaie plusieurs graines et retient « the best complete result ».
Un point de licence corrige beaucoup de pages du web. Avant la 0.9.0, TALA
était fermé, « a paid layout engine, which requires a license for any
commercial use », avec un filigrane sur les rendus sans licence (c'est ce que
dit encore le README de terrastruct/TALA). Depuis la 0.9.0, la note de
release annonce « TALA is now open source and bundled with D2 » et précise
« No separate plugin installation or license key is required » : avec cette
version, aucune clé de licence n'est demandée. La licence exacte de TALA n'a pas
été relevée ici ; D2 lui-même reste en MPL-2.0.
Les trois images suivantes sont produites par D2 0.9.0 à partir de la même
sourceconteneurs.d2 ; seul le moteur change. Comparez d'abord les
dimensions mesurées : dagre donne 2831 x 787, ELK 2722 x 520 et TALA
1359 x 1208. Les deux premiers étalent le schéma sur la largeur, TALA le
replie en un bloc presque carré.
Avec dagre, l'image est très étirée. ELK, ci-dessous, reste aussi large mais bien moins haut (520 contre 787).
Le rendu TALA, enfin, se prête mieux à une page de documentation lue en
portrait : c'est le plus compact pour ce schéma.
Un schéma aide à trancher. Regardez l'arbre de décision : on garde dagre tant
qu'il convient ; on passe à ELK quand les groupes sont imbriqués, que les
liaisons se croisent ou qu'on veut fixer la taille d'un conteneur ; on essaie
TALA pour un schéma d'architecture dense à replier ; si aucun ne donne un
dessin lisible, c'est le schéma qu'il faut simplifier.
Arbre de décision du moteur D2 : si le rendu par défaut dagre est lisible, le garder ; sinon, avec des groupes imbriqués, des liaisons qui se croisent ou des dimensions de conteneur à fixer, choisir ELK ; pour un schéma d'architecture dense à replier, essayer TALA ; si aucun moteur ne convient, simplifier le schéma.Le rendu par défaut (dagre)vous convient ?Garder dagre : rapide, hiérarchique,même résultat d'un poste à l'autreouiGroupes imbriqués, liaisons quise croisent ou dimensions de conteneur ?nonELK : plus mûr, respecte width et height,schéma plus compact en largeurouiSchéma d'architecture dense, àreplier dans une colonne ?nonTALA : conçu pour l'architecture,plusieurs essais (tala-seeds), plus haut que largeouiRevenir à dagre et simplifierle schéma : trop de boîtesnon
Dernier avertissement : le rendu dépend de la version du moteur. D2 0.7.1,
installé sur le poste du site, et celui de Kroki 0.33 ne connaissent pas TALA :
d2 layout n'y liste que dagre et ELK. Une source qui demande
--layout=tala échouera sur un serveur de rendu trop ancien.
Comment changer l'apparence : thèmes et style crayonné ?
Les thèmes sont des palettes numérotées, que d2 themes liste : dix-huit
thèmes clairs (0 Neutral Default, 1 Neutral Grey, 8 Colorblind
Clear, 300 Terminal, 302 Origami, 303 C4, etc.) et deux thèmes sombres,
200 Dark Mauve et 201 Dark Flagship Terrastruct. Le thème 303 s'appelle C4 ;
on le choisit avec --theme=303.
--dark-theme=200 ajoute un deuxième thème : le SVG produit suit alors le mode
sombre du navigateur du lecteur. L'option --sketch donne un trait
« crayonné », utile pour un schéma volontairement provisoire. L'image ci-dessous
est la vue contexte rendue avec --sketch --theme=200.
Un conseil de maintenance : n'écrivez pas de style dans la source. Les
sources de cette leçon n'en contiennent aucun, et le site n'en met jamais dans
les siennes. Une source sans couleur reste lisible en diff et se redessine
sous une autre charte ; le thème s'applique au moment du rendu, par option.
Le format de sortie se déduit de l'extension : d2 contexte.d2 contexte.png,
contexte.pdf ou contexte.txt, ce dernier produisant un dessin en
caractères ASCII, lisible mais dont les libellés sont hachés par les flèches.
Nouveauté de la 0.9.0, les exports PNG, GIF, PDF et PPTX passent par le
« built-in renderer » : plus besoin de navigateur installé pour les produire.
Le SVG reste le meilleur choix pour une page web, vectoriel et léger.
Pour garder des diffs stables, d2 fmt réécrit le fichier dans le style
canonique, et d2 fmt --check signale sans rien modifier. Voici un fichier mal
formaté, tel qu'on le trouve après une saisie rapide :
direction:right
client: Client {shape:person}
boutique:Boutique en ligne
client->boutique: Consulte (HTTPS)
d2 fmt --check nonformate.d2 répond
err: found 1 unformatted file. Run d2 fmt to fix. et sort avec le code 1,
ce qui fait échouer une CI. d2 fmt le
corrige en direction: right, client: Client {shape: person} et
client -> boutique: Consulte (HTTPS). Une ligne WARN qui nomme le fichier
précède le message err:. Enfin d2 validate contexte.d2 vérifie la
syntaxe sans rien dessiner : il répond Success! [...] is valid D2. (avec le
chemin du fichier entre crochets) et sort avec le code 0 quand tout va bien.
Un d2 fmt --check réussi, lui, est silencieux et sort aussi avec le code 0.
Quelles erreurs D2 signale-t-il, et laquelle laisse-t-il passer ?
La première erreur est franche. Dans ce fichier, l'accolade du conteneur
boutique n'est jamais fermée :
direction: right
boutique: Boutique en ligne {
web: Interface web
api: API
client -> boutique.web: Consulte (HTTPS)
d2 validate casse.d2 répond err: github.com/d2lang/d2/d2cli.validateCmd: 3:29: maps must be terminated with } et le code 1. Le rendu donne le même
message, précédé de failed to compile casse.d2. Le détail à retenir : 3:29
désigne la ligne et la colonne de l'accolade ouvrante jamais fermée, pas le
point où D2 s'est aperçu du problème. Pour corriger, on cherche donc l'accolade
ouverte, et non la dernière ligne. Via Kroki, la même erreur remonte sous la
forme Error 400: err: failed to compile -: -:3:29: maps must be terminated with }.
La seconde erreur est un piège sans message. Ici la liaison vise web tout
court alors que web vit dans boutique :
direction: right
boutique: Boutique en ligne {
web: Interface web
api: API
}
client -> web: Consulte (HTTPS)
d2 validate (qui répond Success!) et le rendu réussissent tous deux avec le code 0. D2 a simplement
créé un second nœud web à la racine et relié le client à celui-là, au lieu
de l'interface de la boutique. Seule l'ouverture de l'image le montre : aucune
alerte ne se déclenche. La forme juste est client -> boutique.web. Prenez
l'habitude de regarder le rendu à chaque modification de liaison, et de citer
le chemin complet vers tout nœud imbriqué.
Comment documenter l'image pour les lecteurs qui ne la voient pas ?
D2 n'écrit ni <title> ni <desc> dans ses SVG : le contrôle a porté sur
17 rendus. Tout le texte alternatif d'un schéma D2 doit donc venir de la
page : l'attribut alt de l'image et la phrase qui la précède, ce que lira un
lecteur d'écran. La méthode est détaillée dans
Montrer plutôt qu'écrire ; ici, chaque
schéma porte un alt qui nomme les acteurs et les flux.
Deux contrôles suffisent à garder un dépôt propre : le format (fmt --check)
puis le rendu. Les étapes ci-dessous sont celles du workflow
diagrammes.yml qui accompagne cette série de leçons, réduit à la partie D2.
Le workflow n'a de droits que ce dont il a besoin (permissions: {} par défaut,
contents: read sur le job), l'action de checkout est épinglée sur son commit
avec la version en commentaire, et l'image D2 est épinglée par son digest.
Le fichier passe actionlint et zizmor sans remarque, et les commandes
docker run ont été jouées sur un poste ; l'exécution sur un runner GitHub,
elle, n'a pas été faite. Le piège est dans docs/**/*.d2 : sans l'option
globstar de bash, ** se comporte comme un simple *, et seuls les fichiers
de docs/<dossier>/ sont vus. La CI passerait au vert sans avoir formaté ni
rendu les autres, d'où shopt -s globstar en tête de l'étape.
Quand le rendu doit passer par un serveur plutôt que par un binaire, Kroki
accepte du D2 sur POST /d2/svg (HTTP 200 avec Kroki 0.33.0, qui embarque
D2 0.7.1) : le détail est dans la leçon d'entrée.
Exercice : ajouter un serveur de cache au déploiement
Prenez le fichier deploiement.d2 et ajoutez-y un troisième serveur de la
zone Production : un Serveur de cache (Ubuntu 24.04) qui héberge Redis 8,
lequel contient le nœud Cache de session. L'API lit et écrit dans ce cache
(Lit et écrit (RESP/TCP)). Faites l'exercice avant de lire le corrigé.
Trois points à vérifier une fois la source écrite :
Le cache forme un nœud imbriqué sur trois niveaux (serveur, Redis,
cache de session), à l'intérieur de prod.
La liaison utilise le chemin complet : prod.app.node.api -> prod.cache.redis.session.
d2 fmt --check deploiement.d2 ne signale rien, et l'image montre bien un
seul nœud « Cache de session », dans le serveur de cache.
Le corrigé n'ajoute qu'un bloc dans prod et une liaison ; le reste du fichier
ne change pas. Le voici avec les deux serveurs voisins pour situer l'ajout :
prod: Production {
app: Serveur applicatif\n(Ubuntu 24.04, x2) {
node: Node.js 22 {
api: API
}
}
donnees: Serveur de données\n(Ubuntu 24.04) {
pg: PostgreSQL 17 {
base: Base de données {shape: cylinder}
}
}
cache: Serveur de cache\n(Ubuntu 24.04) {
redis: Redis 8 {
session: Cache de session
}
}
}
prod.app.node.api -> prod.cache.redis.session: Lit et écrit (RESP/TCP)
La liaison s'ajoute à la suite des quatre autres en bas du fichier. Si le
dessin montre un nœud session isolé à la racine, c'est le piège de la section sur
les erreurs : le chemin de la liaison est incomplet.
D2 s'installe en vérifiant la somme SHA256 de l'archive de la release ; on
n'exécute pas un script téléchargé à la volée.
id: Libellé, a -> b: libellé, {shape: …} et \n suffisent pour une vue
contexte.
Un conteneur imbriqué s'ouvre par des accolades ; les liaisons venues de
l'extérieur citent le chemin boutique.web.
Un déploiement s'écrit par imbrication sur trois niveaux et des liaisons
au chemin complet.
dagre est le moteur par défaut, ELK convient aux groupes imbriqués et
TALA, ouvert depuis la 0.9.0, aux schémas d'architecture denses ; le
rendu dépend de la version du moteur.
d2 fmt --check et d2 validate se mettent en CI, mais aucun des deux
n'attrape une liaison vers un nœud mal nommé : on regarde l'image.
3:29 dans une erreur désigne l'accolade ouvrante jamais fermée.
D2 n'écrit pas de <title> ni de <desc> : le texte alternatif vit dans
la page, et le thème s'applique au rendu, pas dans la source.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
6 questions
6 min.
70% requis
Informations
Le chronomètre démarre au clic sur Démarrer
Questions à choix multiples, vrai/faux et réponses courtes
Vous pouvez naviguer entre les questions
Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
⏱️--:--
0 / 6
Vérification
(0/0)
--:--
0%
0Correctes
0À revoir
0:00Temps
-Niveau
Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.