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) orLR(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
| Solution | Language | Rendering Engine | CI Integration | Learning Curve | Typical Use Case |
|---|---|---|---|---|---|
| D2 + ELK | D2 (text) | ELK (graph layout) | GitHub Actions, GitLab CI | Low | Homelab, small‑scale infra |
| Mermaid | Markdown‑style | Built‑in renderer | GitHub Actions, Bitbucket Pipelines | Very low | Documentation, readme diagrams |
| Graphviz | DOT language | Graphviz | GitHub Actions, Azure Pipelines | Medium | Complex dependency graphs |
| PlantUML | UML syntax | PlantUML engine | Various CI systems | Medium | Software 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.
Current State and Future Trends
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.
| Component | Minimum Version | Description |
|---|---|---|
| Operating System | Linux (Ubuntu 22.04 LTS) or macOS 13+ | The rendering pipeline relies on Java runtime; most modern Linux distributions ship a recent JDK. |
| Java Runtime | OpenJDK 11 | Required by the ELK layout engine. |
| Docker | 24.0 or newer | Used to pull the official rendering image and to run containers in the homelab. |
| Git | 2.40 or newer | For cloning repositories and handling pull‑request triggers. |
| Node.js (optional) | 20.x | Useful if you want to run a local preview server for D2 files. |
| GitHub Account | – | To host the repository and enable GitHub Actions workflows. |
| Access to External APIs | – | Required 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
diagramsbranch.
Pre‑Installation Checklist
- Verify Java installation:
java -versionshould report OpenJDK 11. - Pull the D2 rendering Docker image:
docker pull ghcr.io/d2lang/d2:latest. - Create a repository on GitHub (or use an existing one) dedicated to diagrams.
- Add a
diagrams/directory to store D2 source files. - 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
TBdeclares a top‑to‑bottom layout direction.node <Name>defines a node with optional attributes.colorsets the fill color using a hex value.iconpoints 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
checkoutstep uses the built‑inGITHUB_TOKENto authenticate. - Docker is used to run the official D2 rendering image, avoiding any local Java installation.
- The
rendercommand converts each.d2file to a PNG placed underdiagrams/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