Post

I Stopped Hand-Drawing My Homelab Diagram Now It Rebuilds Itself From Code On Every Push

I Stopped Hand-Drawing My Homelab Diagram Now It Rebuilds Itself From Code On Every Push

I Stopped Hand‑Drawing My Homelab Diagram Now It Rebuilds Itself From Code On Every Push

INTRODUCTION

If you have ever spent hours hunched over a whiteboard or a static drawing tool, only to watch your homelab topology crumble the moment a single service changes, you know the pain of a stale diagram. The frustration is real: a network map that should be a single source of truth becomes a moving target, forcing you to chase inconsistencies after every upgrade, patch, or new container spin‑up.

In modern DevOps and self‑hosted environments, the diagram is more than a visual aid — it is a contract between teams, a reference for troubleshooting, and sometimes even a compliance artifact. When that contract is maintained manually, drift is inevitable, and the cost of catching up can quickly outweigh the time spent drawing in the first place.

This guide walks you through a concrete solution that many homelab enthusiasts and small‑scale production operators have adopted: diagram‑as‑code. By representing network topology, service relationships, and infrastructure layout in a plain‑text file, you can let a build pipeline automatically render an up‑to‑date visual representation on every push. The result is a diagram that never drifts, is version‑controlled, and can be reviewed just like any other source file.

In the sections that follow you will learn:

  • What diagram‑as‑code actually is and why it matters for homelab and self‑hosted setups.
  • The history and core concepts behind the D2 diagram language and the ELK layout engine.
  • How to set up a reproducible build environment using GitHub Actions.
  • Step‑by‑step instructions for installing, configuring, and optimizing the rendering pipeline.
  • Practical usage patterns, monitoring strategies, and troubleshooting tips.

Whether you are running a personal lab of Raspberry Pi nodes, a small Kubernetes cluster for testing, or a modest set of Docker‑compose services that power home automation, the principles outlined here will help you automate a critical piece of documentation without adding operational overhead.

By the end of this article you should be able to commit a single line describing a new service, watch the diagram regenerate itself on the next push, and have confidence that your README image will always reflect the real state of your environment.


UNDERSTANDING THE TOPIC

What Is Diagram‑As‑Code?

Diagram‑as‑code refers to the practice of describing visual diagrams using plain‑text specifications. Instead of dragging shapes around in a GUI, you write a concise, declarative file that defines nodes, connections, labels, and styling. Tools that support this approach parse the file, apply layout algorithms, and output an image that can be embedded in documentation, dashboards, or code repositories.

The primary benefits are:

  • Version control friendliness – The diagram file lives alongside your code, making diffs visible in pull requests.
  • Automation potential – CI pipelines can render the diagram automatically, eliminating manual steps.
  • Consistency – All team members use the same source of truth, reducing misinterpretation.
  • Reproducibility – Rendering engines are deterministic, so the same input always produces the same output.

The D2 Language and Its Ecosystem

D2 (pronounced “diagram‑to‑diagram”) is an open‑source, markdown‑compatible language designed for drawing diagrams directly in text files. It blends the simplicity of markdown with the expressive power of graph notation. A D2 file can describe network topologies, system architectures, or any diagram where nodes and edges convey relationships.

Key features of D2 include:

  • Declarative syntax – Nodes are declared with attributes such as shape, color, and label.
  • Layout directives – You can hint at the desired arrangement using keywords like TB (top‑to‑bottom) or LR (left‑to‑right).
  • Styling hooks – Colors, fonts, and icons can be customized per node or globally.

Once a D2 file is written, it can be fed to a rendering engine that produces SVG or raster images. The most common pipeline uses the ELK layout engine, a powerful, open‑source graph layout library that can be invoked from a command‑line interface. ELK processes the D2 source, applies a layout algorithm, and outputs an SVG that can be rasterized to PNG for embedding in README files.

Why This Approach Fits Homelab Environments

Homelab setups often involve a mix of Docker containers, Kubernetes pods, virtual machines, and physical hardware. The relationships between these components are fluid: a new container may appear, an old one may be retired, or a network interface may be reconfigured. Traditional diagram tools struggle with this fluidity because they require manual updates.

By moving the diagram definition into code, you gain:

  • Immediate feedback – Every push triggers a rebuild, so the diagram reflects the latest state.
  • Auditability – Changes are captured in Git history, making it trivial to trace when a particular service was added or removed.
  • Scalability – Adding dozens of nodes does not require redrawing; you simply append lines to the D2 file.
  • Portability – The same D2 source works on any platform that can run the rendering pipeline, be it a local workstation or a CI runner.

Comparison With Alternative Solutions

SolutionLanguageRendering EngineCI IntegrationLearning CurveTypical Use Case
D2 + ELKD2 (text)ELK (graph layout)GitHub Actions, GitLab CILowHomelab, small‑scale infra
MermaidMarkdown‑styleBuilt‑in rendererGitHub Actions, Bitbucket PipelinesVery lowDocumentation, readme diagrams
GraphvizDOT languageGraphvizGitHub Actions, Azure PipelinesMediumComplex dependency graphs
PlantUMLUML syntaxPlantUML engineVarious CI systemsMediumSoftware design diagrams

While Mermaid is excellent for quick markdown snippets, it lacks the fine‑grained control over layout that D2 offers when combined with ELK. Graphviz provides powerful layout algorithms but requires learning the DOT syntax, which can be verbose for network diagrams. D2 strikes a balance: a concise syntax that is easy to read, yet expressive enough to describe network topologies, service meshes, and nested architectures.

The diagram‑as‑code movement has matured rapidly. D2’s ecosystem now includes:

  • GitHub Action templates that automatically render diagrams on push events.
  • Docker images that bundle the ELK engine, making local rendering possible without installing Java or native libraries.
  • Community extensions such as icon packs for common services (e.g., Prometheus, Grafana, Home Assistant).

Looking ahead, we can expect tighter integration with infrastructure‑as‑code tools like Terraform or Ansible, allowing the diagram to be generated directly from Terraform state files or Ansible inventory data. This would close the loop between provisioning and documentation, ensuring that the visual representation always mirrors the declared infrastructure.


PREREQUISITES

Before you can automate diagram generation, you need a few foundational components on your workstation or CI runner. The following checklist ensures a smooth setup.

ComponentMinimum VersionDescription
Operating SystemLinux (Ubuntu 22.04 LTS) or macOS 13+The rendering pipeline relies on Java runtime; most modern Linux distributions ship a recent JDK.
Java RuntimeOpenJDK 11Required by the ELK layout engine.
Docker24.0 or newerUsed to pull the official rendering image and to run containers in the homelab.
Git2.40 or newerFor cloning repositories and handling pull‑request triggers.
Node.js (optional)20.xUseful if you want to run a local preview server for D2 files.
GitHub AccountTo host the repository and enable GitHub Actions workflows.
Access to External APIsRequired for fetching icon packs from the D2 community repository.

Network and Security Considerations

  • Outbound internet access – The rendering pipeline may need to download icons or Docker images from public registries. Ensure your firewall permits HTTPS traffic to raw.githubusercontent.com, github.com, and Docker Hub.
  • Rate limiting – If you plan to fetch icons on every build, consider caching them locally to avoid hitting API limits.
  • Least privilege – The GitHub Action should run with minimal permissions, using a dedicated bot token that only has rights to push to the diagrams branch.

Pre‑Installation Checklist

  1. Verify Java installation: java -version should report OpenJDK 11.
  2. Pull the D2 rendering Docker image: docker pull ghcr.io/d2lang/d2:latest.
  3. Create a repository on GitHub (or use an existing one) dedicated to diagrams.
  4. Add a diagrams/ directory to store D2 source files.
  5. Set up a GitHub Actions workflow file at .github/workflows/render-diagrams.yml.

Once these prerequisites are satisfied, you can proceed to the installation and setup phase.


INSTALLATION & SETUP

Step 1 – Clone the Repository

1
2
git clone https://github.com/yourusername/homelab-diagrams.git
cd homelab-diagrams

Step 2 – Create a D2 Diagram File

Create a file named network.d2 inside the diagrams/ directory. The following example illustrates a simple network with three services: a router, a monitoring server, and a home‑assistant container.

# network.d2
TB
node Router {
    shape: rectangle
    label: "Router"
    color: #2c3e50
    icon: url(https://raw.githubusercontent.com/d2lang/d2-icons/main/icons/router.svg)
}
node Monitoring {
    shape: ellipse
    label: "Monitoring"
    color: #3498db
    icon: url(https://raw.githubusercontent.com/d2lang/d2-icons/main/icons/server.svg)
}
node HomeAssistant {
    shape: ellipse
    label: "Home Assistant"
    color: #2ecc71
    icon: url(https://raw.githubusercontent.com/d2lang/d2-icons/main/icons/home-assistant.svg)
}
Router --> Monitoring
Monitoring --> HomeAssistant

Explanation of the syntax

  • TB declares a top‑to‑bottom layout direction.
  • node <Name> defines a node with optional attributes.
  • color sets the fill color using a hex value.
  • icon points to an external SVG that will be overlaid on the node.
  • ` –> ` draws a directed edge from the source node to the target node.

Step 3 – Add a GitHub Actions Workflow

Create the file .github/workflows/render-diagrams.yml with the following content. This workflow triggers on every push to the main branch, renders all .d2 files, and commits the generated PNGs back to the repository.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
name: Render Diagrams

on:
  push:
    branches: [ main ]

jobs:
  render:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          fetch-depth: 0

      - name: Set up Docker
        uses: docker/setup-buildx-action@v3

      - name: Pull rendering image
        run: docker pull ghcr.io/d2lang/d2:latest

      - name: Render diagrams
        env:
          DIAGRAM_DIR: diagrams
          OUTPUT_DIR: diagrams/rendered
        run: |
          mkdir -p "$OUTPUT_DIR"
          for d2file in $(find "$DIAGRAM_DIR" -name '*.d2'); do
            png="${OUTPUT_DIR}/$(basename "${d2file%.*}").png"
            docker run --rm \
              -v "$(pwd)/$DIAGRAM_DIR:/src" \
              -v "$(pwd)/$OUTPUT_DIR:/out" \
              ghcr.io/d2lang/d2:latest \
              render -i "/src/$(basename "$d2file")" -o "/out/$(basename "${d2file%.*}").png" -f png
          done

      - name: Commit rendered images
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add diagrams/rendered/*.png
          if git diff --cached --quiet; then
            echo "No changes to commit."
          else
            git commit -m "chore: update rendered diagrams"
            git push origin HEAD:${{ github.ref }}
          fi

Key points in the workflow

  • The checkout step uses the built‑in GITHUB_TOKEN to authenticate.
  • Docker is used to run the official D2 rendering image, avoiding any local Java installation.
  • The render command converts each .d2 file to a PNG placed under diagrams/rendered.
  • After rendering, the workflow checks for changes and pushes them back to the same branch.

Step 4 – Verify the Rendering Locally (Optional)

If you prefer

This post is licensed under CC BY 4.0 by the author.