Supercharge your GraphViz
I love a good software diagram. I'm a believer that if you can't draw a diagram of something, then it might not be possible. In the software industry the role of diagrams is pervasive and that has been true for decades despite so many other disruptions. Our need to visually communicate and understand software isn't going anywhere.
However, that is not to say that nothing has changed. Being that developers are the primary consumers of these diagrams, it makes sense that a lot of effort has been put into bringing more organization and sanity to managing diagrams. The number one issue with diagrams is ... I'm kidding there are a bunch of common issues: lack of clarity, inconsistent design patterns, or just becoming outdated.
Let's explore the subject of diagrams as code. Ideally by documenting our diagrams alongside our codebase they will enjoy several benefits:
- Having one source-of truth ( living in a git repository )
- Being version controlled, tracking who changed a diagram, when and why
- Being treated as code = more likely to be updated regularly and seen as part of the application
The Exercise
We're going to be taking a dive with GraphViz, a well-established tool that can be integrated alongside nearly any project ( software or otherwise ).
In this post I'll explain how you can utilize GraphViz in a modern way, leveraging containers and later CI behaviors via Github Actions.
- Basic syntax and rendering
- automating generation
- turning it up to LLM
You Will Need
- Docker
- The
nshine/dot
image [github] - A workspace or existing project to diagram
Phase 1: Basic Diagram Generation
We will use *.dot files to define our diagrams. The syntax of "DOT language" is pretty straightforward. Diagrams are made of nodes and edges, they have a flow direction, you can control the styling. That is about all you need to know at this point. Take my example below and modify to suit your use case.
digraph TrainingPipeline {
// Graph attributes
rankdir=TB;
bgcolor="white";
node [shape=box, style="rounded,filled", fillcolor="gray"];
edge [color="black", fontsize=10];
// Define nodes
start [label="Start", shape=ellipse, fillcolor="green"];
process1 [label="Model Simulation"];
process2 [label="Generate Training Data"];
process4 [label="Develop CV models"];
process5 [label="Training"];
process6 [label="Validation"];
decision [label="Generalizes?", shape=diamond, fillcolor="yellow"];
process7 [label="Implementation"];
// Define edges
start -> process1;
process1 -> process2;
process2 -> process4;
decision -> process7 [label="Yes"];
decision -> process4 [label="No"];
process4 -> process5;
process5 -> process6;
process6 -> decision;
//error -> end;
// styling, which share a rank/row
{rank=same; process1, process2}
{rank=same; process4, process5, process6}
{rank=same; process6, process7}
}
By passing this DOT file to the docker container we can generate a graph:
cat training.dot | docker run --rm -i nshine/dot > output/graph.png
The incredible utility of Docker is almost lost in this simplicity: Your diagram definition goes in, an image comes out:

Phase 2: Automation
We'll use Github Actions to setup a simple workflow that renders an output diagram ( PNG format as an artifact ) in response to a push of the diagram's source ( DOT ) file.
This can be accomplished through the Github UI, but can also be done in plain code since Github workflows are defined as YAML files inside .github/workflows/
I was pleased to find that there are already some community-contributed solutions for graphviz, including this action
Create a new file in your repo:
<your_repo>/.github/workflows/render-diagram.yml
name: Render GraphViz Diagram
on:
push:
branches: [ main, master ]
paths:
- 'diagram.dot'
- '.github/workflows/render-diagram.yml'
jobs:
render:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Graphviz
uses: ts-graphviz/setup-graphviz@v2
- name: Render diagram to PNG
run: |
dot -Tpng diagram.dot -o diagram.png
- name: Upload diagram artifact
uses: actions/upload-artifact@v4
with:
name: rendered-diagram
path: diagram.png
retention-days: 30
So that will run 4 actions:
- Checkout the repository itself
- Setup GraphViz
- Run
dot
to render the diagram from thediagram.dot
file - Save the output as a workflow artifact

diagram.dot
After the workflow completes ( successfully ), you can download the artifacts.

A more realistic workflow might:
- commit the outputs back to the repository
- commit them to another repository for documentation
- embed them into documentation of other formats
Phase 3: Taking it all the way
Do you already know where I'm going with this? Being that DOT is a well-documented, much-used, text-based format, there are plenty of examples that have been studied by large language models. This is like a home-field advantage for these technologies.
Behold this actual example:
Prompt to ChatGPT:
generate a graphviz DOT-formatted file that includes all they key players, locations, and pieces of evidence in the OJ Simpson trial, being sure to include edge links where appropriate and using a color-coding scheme to distinguish node types
Outputs:
digraph OJSimpsonTrial {
rankdir=LR;
node [style=filled];
// Node Styles
node_person [fillcolor=lightblue];
node_location [fillcolor=lightyellow];
node_evidence [fillcolor=lightpink];
node_event [fillcolor=lightgrey];
// People
"OJ Simpson" [label="O.J. Simpson", group=person, fillcolor=lightblue];
"Nicole Brown" [label="Nicole Brown Simpson", group=person, fillcolor=lightblue];
"Ron Goldman" [label="Ron Goldman", group=person, fillcolor=lightblue];
"Mark Fuhrman" [label="Det. Mark Fuhrman", group=person, fillcolor=lightblue];
"Kato Kaelin" [label="Kato Kaelin", group=person, fillcolor=lightblue];
"Robert Shapiro" [label="Robert Shapiro", group=person, fillcolor=lightblue];
"Johnnie Cochran" [label="Johnnie Cochran", group=person, fillcolor=lightblue];
"Marcia Clark" [label="Marcia Clark", group=person, fillcolor=lightblue];
"Lance Ito" [label="Judge Lance Ito", group=person, fillcolor=lightblue];
// Locations
"Bundy Drive" [label="875 S. Bundy Dr.", group=location, fillcolor=lightyellow];
"Rockingham Estate" [label="Rockingham Estate", group=location, fillcolor=lightyellow];
"Chicago Hotel" [label="Chicago Hotel", group=location, fillcolor=lightyellow];
"Courtroom" [label="L.A. Courthouse", group=location, fillcolor=lightyellow];
// Evidence
"Bloody Glove" [label="Glove (Bloody)", group=evidence, fillcolor=lightpink];
"Bloody Shoeprints" [label="Shoeprints (Size 12)", group=evidence, fillcolor=lightpink];
"Bronco" [label="White Ford Bronco", group=evidence, fillcolor=lightpink];
"DNA Evidence" [label="DNA Evidence", group=evidence, fillcolor=lightpink];
"Bloody Socks" [label="Bloody Socks", group=evidence, fillcolor=lightpink];
// Events
"Murder" [label="Double Homicide", group=event, fillcolor=lightgrey];
"Chase" [label="Bronco Chase", group=event, fillcolor=lightgrey];
// Relationships
"Nicole Brown" -> "OJ Simpson" [label="ex-wife"];
"Ron Goldman" -> "Nicole Brown" [label="acquaintance of"];
"OJ Simpson" -> "Bronco" [label="owned"];
"Bronco" -> "Chase" [label="used in"];
"OJ Simpson" -> "Chase" [label="fled in"];
"Murder" -> "Bundy Drive" [label="occurred at"];
"Nicole Brown" -> "Murder" [label="victim"];
"Ron Goldman" -> "Murder" [label="victim"];
"Bloody Glove" -> "Bundy Drive" [label="found at"];
"Bloody Glove" -> "Rockingham Estate" [label="matched with"];
"Mark Fuhrman" -> "Bloody Glove" [label="found by"];
"DNA Evidence" -> "OJ Simpson" [label="matched"];
"Bloody Socks" -> "OJ Simpson" [label="found in home of"];
"Bloody Shoeprints"-> "Bundy Drive" [label="found at"];
"OJ Simpson" -> "Bloody Shoeprints" [label="matched size"];
"Kato Kaelin" -> "Rockingham Estate" [label="guest at"];
"OJ Simpson" -> "Rockingham Estate" [label="resided at"];
"OJ Simpson" -> "Chicago Hotel" [label="traveled to"];
"OJ Simpson" -> "Courtroom" [label="tried at"];
"Robert Shapiro" -> "OJ Simpson" [label="defended"];
"Johnnie Cochran" -> "OJ Simpson" [label="defended"];
"Marcia Clark" -> "OJ Simpson" [label="prosecuted"];
"Lance Ito" -> "OJ Simpson" [label="presided over trial of"];
}
Passed through the docker container we get:

The speed and convenience of this can't be understated. ( Also consider how quickly you just imported a graph database )
Or on a lighter note, a (GPT-authored) flowchart of Pokemon evoutions:

Phase 4: Getting Meta
Asking for diagrams of existing codebases is generating decent results. I think it is fair to pose the question whether a nearly-accurate diagram is better than none at all? ( Please disagree in the comments 😄 )
A diagram of my own diagram-generating project, written by Claude via Github Copilot and visualized with GraphViz :

Really, this should be simpler. With more prompting and some improvements on how to achieve a standard template it could probably be reliable.
I'm not saying that robots are going to take over this job, you can see that an orchestration like this still needs a person at the helm. We have to take the obvious wins from these tools and then still add our human value.
After one round of feedback, reminding it to take the user's perspective, this is much more usable:

For the convenience and utility I might decide to make this diagram, instead of just not making it at all.