Diagram as Code with D2!

Documentation is always important, but always takes too much time

Ever had an issue where a new installation is completed, but there's just no time to update the ol' Visio diagrams?

Manually composed diagrams always possess a certain art to them, but the hours per unit of documentation isn't always worthwhile (particularly when prototyping a solution).

Deploying Infrastructure-As-Code should involve deployment of a mature, production-ready implementation; subjecting a coded prototype to rigorous and comprehensive testing prior to release contributes directly to end-to-end reliability. Infrastructure consumers demand ever-increasing improvements to reliability and features - mostly because they are being subjected to higher standards as well - which will require some changes to how infrastructure is delivered.

Some of these changes are positive! The inevitable result of this shift is the elimination of artisanal, hand-crafted infrastructure customized to perfection for customers, trading for a consistent, predictable infrastructure.

Consistent infrastructure without documentation, however, will confuse and deter aspirant consumers - confidence is key. I'd propose that we ought to explore completely automated ways of generated documentation in IT, and network/infrastructure diagrams are no exception.


Let's test out a new diagramming tool - D2. Here's a summary of the features it provides:

  • Installable via the Go package manager
  • Supports server-side rendering
  • Configurable Color Theming
  • Object Grouping (hierarchical)
  • SVG Export (no more setting and calculating your own canvas size!)
  • Intuitive connectors
  • Preview plugin for Visual Studio Code
  • Language API support
  • Custom object types (icons must be accessible via HTTP from the diagram server)

Deploying the tool

Installation instructions for D2 are listed in their GitHub repository - this is probably the easiest way to execute the installation:

1go install oss.terrastruct.com/d2@latest

Go's built-in package manager is in charge of the installation at this point, and the platform installs cleanly on Windows and Linux.

Drawing Diagrams

Let's start with a simple diagram:

1hola! -> como estas!

Visual Studio Code can provide a preview of this diagram in either Markdown or as a .d2 file, but GitHub-flavored markdown won't render it. This is still super useful, and seems to handle special characters really well. Let's try to render it:

1echo 'hola! -> como estas!' > diagram.d2
2d2 diagram.d2

This will produce an SVG:

D2 Example Diagram

D2 is extremely fast at rendering vector graphics, but it can support PNG and PDF output as well. The CLI interpreter detects the file output settings from the extension:

1d2 diagram.d2 diagram.png

It's slower, and consumes more bandwidth to draw PNG - vector graphics are extremely efficient, particularly with diagrams. We can apply themes as well:

1d2 diagram.d2 --theme=101

Themed diagram

Custom themes are also supported.

Adding Icons

D2 supports usage of icons, but they have to be accessible via HTTP. Terrastruct provides an Icons Library to help out - and custom stencils would have to be stored somewhere for rendering (either as SVG or PNG).

Let's try drawing a diagram with icons:

 1'User': {
 2    icon: https://icons.terrastruct.com/essentials%2F365-user.svg
 3    shape: circle
 5'Internet access': {
 6    icon: https://icons.terrastruct.com/essentials%2F214-worldwide.svg
 7    shape: hexagon
 9'User' -> 'Internet access': {
10    label: wants

Here, we introduced several new constructs. D2's language is very intuitive, with clear inferences from JSON and YAML - and we use key-value pairs to identify what we should draw. D2 supports hierarchical nesting as well. On the third section, we added a "connector" by using the -> directive between the two named objects:

Diagram with Icons

It's also possible to attach object attributes by misusing the class shape type:

1'Router01': {
2    icon: https://icons.terrastruct.com/essentials%2F092-network.svg
3    shape: hexagon
5'Router01 Interfaces': {
6    shape: class
7    'Ethernet1/41':
9'Router01' -> 'Router01 Interfaces'

Diagram with Classes

This tool has tremendous illustrative potential when built with automation. Let's imagine a server-side workflow that could integrate rendering of an object as part of the CI workflow.

Actually, instead of imagining it, let's try diagramming it instead:

 1# Object Definitions
 2'Continuous Delivery Stack': {
 3    'CI Tool': {
 4        icon: https://www.jenkins.io/images/logos/pixelart/jenkins-pixelart-256.png
 5        shape: hexagon
 6    }
 7    'Ansible': {
 8        icon: https://upload.wikimedia.org/wikipedia/commons/2/24/Ansible_logo.svg
 9        shape: hexagon
10    }
11    GitHub: {
12        icon: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
13        shape: circle
14    }
16'Rendering': {
17    'Jinja2': {
18        icon: https://jinja.palletsprojects.com/en/3.1.x/_images/jinja-logo.png
19        shape: hexagon
20    }
21    'D2': {
22        icon: https://icons.terrastruct.com/assets/icons/d2-logo.svg
23    }
25'Node Definition': {
26    'Object': {
27        icon: https://upload.wikimedia.org/wikipedia/commons/thumb/5/5a/Official_YAML_Logo.svg/64px-Official_YAML_Logo.svg.png
28    }
29    'Object Facts': {
30        shape: class
31        name: Router01
32        baseline: '2023001'
33        'Ethernet1/41': ''
34    }
35    'Node Configuration': {
36        icon: https://icons.terrastruct.com/aws%2FNetworking%20&%20Content%20Delivery%2FAmazon-VPC_Router_light-bg.svg
37    }
39# Connectors
40'Continuous Delivery Stack'.'CI Tool' -> 'Continuous Delivery Stack'.'GitHub': 'Fetch Source Code'
41'Continuous Delivery Stack'.'CI Tool' -> 'Continuous Delivery Stack'.'Ansible': 'Execute Playbooks'
42'Continuous Delivery Stack'.'Ansible' -> 'Node Definition'.'Object': 'Get Node Facts'
43'Node Definition'.'Object Facts' -> 'Node Definition'.'Object'
44'Continuous Delivery Stack'.'Ansible' -> 'Rendering'.'Jinja2': 'Render Configuration'
45'Continuous Delivery Stack'.'Ansible' -> 'Rendering'.'Jinja2': 'Render Diagram Templates'
46'Continuous Delivery Stack'.'Ansible' -> 'Rendering'.'D2': 'Execute D2 Draw'
47'Rendering'.'Jinja2' -> 'Rendering'.'D2'
48'Rendering'.'Jinja2' -> 'Rendering'.'Node Configuration'

Rendering objects as code

D2 proves to be an excellent method of automatically generating diagrams. The diagram above is automatically positioned, so it won't have the "polish" that a manually generated diagram would have, but it also takes a fraction of the effort. This is a game-changing tool; engineers should use this tool to generate and render solution documentation on-every IaC update, and then rely on manual documentation for higher-value work like executive presentations or sales pitches.

For the rest of the use cases, let's save time by automating work where that artisanal, curated experience doesn't help us. In future posts we'll examine ways to document infrastructure.

Posts in this Series