About this course
<p>Creating system architecture design diagrams is an important part of technical documentation. As system complexity grows, clarity within teams becomes even more important.</p>
<p>But creating diagrams from scratch using canvas-like tools isn't always efficient.</p>
<p>Luckily, there's an alternative way to create diagrams using a diagram-as- code approach. In this approach, you explain the diagram components using a predefined syntax instead of drawing them.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-benefits-of-diagram-as-code">Benefits of Diagram-as-Code</a></p>
</li>
<li><p><a href="#heading-prerequisites-and-installation">Prerequisites and Installation</a></p>
</li>
<li><p><a href="#heading-what-is-diagrams">What is Diagrams?</a></p>
</li>
<li><p><a href="#heading-your-first-diagram">Your First Diagram</a></p>
</li>
<li><p><a href="#heading-how-to-connect-nodes-and-data-flow">How to Connect Nodes and Data Flow</a></p>
</li>
<li><p><a href="#heading-grouping-data-flows">Grouping Data Flows</a></p>
</li>
<li><p><a href="#heading-clusters">Clusters</a></p>
</li>
<li><p><a href="#heading-edges">Edges</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-benefits-of-diagram-as-code">Benefits of Diagram-as-Code:</h2>
<ul>
<li><p>Changes are tracked via version control.</p>
</li>
<li><p>Saves time as drawing from scratch or a tool takes time due to manual formatting.</p>
</li>
<li><p>Most diagram-as-a-code tools support standard tech icons like database, storage bucket, load balancers, and so on.</p>
</li>
<li><p>Diagrams can be generated automatically in CI/CD pipelines or documentation builds using code.</p>
</li>
</ul>
<h2 id="heading-prerequisites-and-installation">Prerequisites and Installation</h2>
<p>In this tutorial, you'll learn how to draw diagrams using the Python <strong>Diagrams</strong> library. The basic prerequisites are:</p>
<ul>
<li><p><strong>Python 3.7+</strong></p>
</li>
<li><p><strong>pip</strong> for installing Python packages</p>
</li>
<li><p><strong>Graphviz</strong> installed on your operating system for rendering output images</p>
</li>
<li><p>The Python package:</p>
<pre><code class="language-bash">pip install diagrams
</code></pre>
</li>
<li><p>A terminal to run files like:</p>
<pre><code class="language-bash">python diagram.py
</code></pre>
</li>
</ul>
<h2 id="heading-what-is-diagrams">What is Diagrams?</h2>
<p>Diagrams is a Python library that lets you draw cloud system architecture in Python code. It supports different providers like Azure, GCP, AWS, and so on.</p>
<p>By the end of this tutorial, you'll be able to understand how this diagram was made:</p>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/e755b390-8615-4ea8-8a6f-d3bebfb8bc9a.png" alt="System architecture diagram for a video processing platform using AWS icons" style="display: block;" width="600" height="400" loading="lazy">
<h2 id="heading-your-first-diagram">Your First Diagram</h2>
<p>First, make sure your setup is complete as per the prerequisites. Then create a Python file with the <code>.py</code> extension and add this code to it:</p>
<pre><code class="language-python">from diagrams import Diagram
from diagrams.aws.compute import EC2
with Diagram("Practice Architecture"):
EC2("api-server")
</code></pre>
<p>Let's understand the program line by line.</p>
<p>First, we have import <code>Diagram</code>:</p>
<pre><code class="language-python">from diagrams import Diagram
</code></pre>
<p>Think of <code>Diagram</code> as a blank canvas and everything you draw goes inside it. <code>Diagram</code> represents a global diagram context.</p>
<p>Then we import a node type. A <strong>node</strong> represents one component in your architecture. For example, ECS, lambda, Service, and firewall are resource types.</p>
<pre><code class="language-python">from diagrams.aws.compute import EC2
</code></pre>
<p>A node object consists of three parts: <strong>provider</strong>, <strong>resource type</strong>, and <strong>name</strong>. In the example above, the <code>EC2</code> is a node of resource type <code>compute</code> which is provided by the <code>aws</code> provider.</p>
<p>This imports the <code>EC2</code> node from the AWS compute group. We'll use this class to create EC2 nodes.</p>
<p><code>diagrams.aws.compute</code> is the import path and it tells you where the <code>EC2</code> icon is located.</p>
<pre><code class="language-plaintext">diagrams
└── aws
└── compute
└── EC2
</code></pre>
<p>You can explore more official node types for different providers <a href="https://diagrams.mingrammer.com/docs/guides/node">here</a>.</p>
<p>Let's say you want to use the Analytics component from AWS in your diagram. First, you'll search for Analytics from the list of node types shared above. Your import statement will look like this: <code>from diagrams.aws.analytics import Analytics</code>.</p>
<p>Then create the diagram context using <code>with Diagram</code>:</p>
<pre><code class="language-python">with Diagram("Practice Architecture"):
</code></pre>
<p>Coming back to your example, this creates a diagram named <code>Practice Architecture</code>. Everything indented underneath <code>with Diagram</code> belongs to that diagram.</p>
<p>Now create a node:</p>
<pre><code class="language-python">EC2("api-server")
</code></pre>
<p>This creates one EC2 node with the label displayed as <code>api-server</code> underneath it.</p>
<p>And finally, run the code. If you saved your Python code file as <code>test.py</code>, run it using <code>python test.py</code>. Diagrams generates an image file for you in the current folder:</p>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/5fc4d4d8-d6f1-4190-ab66-748ca3372a07.png" alt="An example of a single node using the Nodes object" style="display: block;" width="600" height="400" loading="lazy">
<p>In short, this is what the process looks like:</p>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/c2670786-1dc1-4353-9d42-502ac459d7d0.png" alt="Process flow for generating image using Diagrams library and Graphviz" style="display: block;" width="600" height="400" loading="lazy">
<p>Note that you're describing infrastructure instead of manually drawing it. You aren't thinking about drawing and arranging icons. Instead, you just code <code>EC2("api-server")</code> and Diagrams decides how to render it.</p>
<p>We can slightly expand the above example to include two EC2 instances:</p>
<pre><code class="language-plaintext">from diagrams import Diagram
from diagrams.aws.compute import EC2
with Diagram("Practice Architecture"):
EC2("api-server")
EC2("worker")
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/5c262441-301e-4083-85b1-3eb992886a5b.png" alt="An example of two nodes using the Nodes object" style="display: block;" width="600" height="400" loading="lazy">
<p>Note that you can assign your node instances to variables. In this way, it's easy to reference them.</p>
<p>The example below demonstrates variable assignment:</p>
<pre><code class="language-python">from diagrams import Diagram
from diagrams.aws.compute import EC2
with Diagram("Practice Architecture"):
api_server = EC2("api-server")
worker = EC2("worker")
</code></pre>
<h2 id="heading-how-to-connect-nodes-and-data-flow">How to Connect Nodes and Data Flow</h2>
<p>You've learned how to create nodes. Now you'll see how to connect them.</p>
<p>Directions are represented by the following symbols:</p>
<ul>
<li><p><code>>></code> for left-to-right flow</p>
</li>
<li><p><code><<</code> for the opposite direction (right-to-left)</p>
</li>
<li><p><code>-</code> for an undirected connection</p>
</li>
</ul>
<p>Let's see an example of these together:</p>
<pre><code class="language-python">from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB
with Diagram("The three directions"):
load_balancer = ELB("load-balancer")
web_server = EC2("web-server")
database = RDS("database")
backup_server = EC2("backup-server")
load_balancer >> web_server
web_server << database
database - backup_server
</code></pre>
<p>Pay attention to connections between nodes:</p>
<pre><code class="language-py"># Connects load balancer to web server with a left to right arrow
load_balancer >> web_server
# Connects web server and database using a right to left arrow
web_server << database
# connects database and web server using a straight line
database - backup_server
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/2f60d3c8-019c-4c39-a56a-063339dcf9a2.png" alt="Connecting nodes using three ways- left to right arrow, right to left arrow and a directionless connection" style="display: block;" width="600" height="400" loading="lazy">
<h2 id="heading-grouping-data-flows">Grouping Data Flows</h2>
<p>You can also connect one node to multiple nodes using a Python list. This way you don't need to connect individual nodes one by one to another node.</p>
<p>Let's say you want to implement this architecture:</p>
<pre><code class="language-plaintext"> ┌──► web-1 ───┐
load-balancer ──────┼──► web-2 ───┼──► database
└──► web-3 ───┘
</code></pre>
<p>First, create a load balancer:</p>
<pre><code class="language-python">load_balancer = ELB("entry-point")
</code></pre>
<p>Then create several servers. Instead of creating three separate variables like this:</p>
<pre><code class="language-python">server1 = EC2("web-1")
server2 = EC2("web-2")
server3 = EC2("web-3")
</code></pre>
<p>You can put them inside a Python list:</p>
<pre><code class="language-python">web_servers = [
EC2("web-1"),
EC2("web-2"),
EC2("web-3")
]
</code></pre>
<p>Then connect LB node to the listload_balancer >> web_servers like this:</p>
<pre><code class="language-py">load_balancer >> web_servers
</code></pre>
<p>This way, you don't need to manually write:</p>
<pre><code class="language-py">load_balancer >> web_servers[0]
load_balancer >> web_servers[1]
load_balancer >> web_servers[2]
</code></pre>
<p>Now, connect the whole list to the database:</p>
<pre><code class="language-py">web_servers >> database
</code></pre>
<p>This approach is useful for reducing redundancy in code. It helps in targeting repeated components like multiple pods, workers, and so on.</p>
<p>The complete code looks like this:</p>
<pre><code class="language-py">from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB
with Diagram("Scaled Web App"):
load_balancer = ELB("entry-point")
web_servers = [
EC2("web-1"),
EC2("web-2"),
EC2("web-3")
]
database = RDS("orders-db")
load_balancer >> web_servers >> database
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/ada08440-4aa5-4c5f-a9e7-87a046b63a15.png" alt="diagram showing a load balancer connected to three web servers which are in turn connected to a database" style="display: block;" width="600" height="400" loading="lazy">
<h2 id="heading-clusters">Clusters</h2>
<p>You can group related nodes inside a labeled box using clusters. You can still connect nodes inside a cluster to outside nodes.</p>
<p>Grouping is necessary to demonstrate what belongs together. It helps represent concepts such as:</p>
<ul>
<li><p>application tiers</p>
</li>
<li><p>database tiers</p>
</li>
<li><p>availability zones</p>
</li>
<li><p>regions</p>
</li>
<li><p>environments</p>
</li>
<li><p>microservice groups</p>
</li>
<li><p>worker groups</p>
</li>
</ul>
<p>This is the syntax to create a cluster:</p>
<pre><code class="language-plaintext">with Cluster("Cluster name"):
</code></pre>
<p>Anything indented under this will be part of the cluster.</p>
<p>Here's an example to demonstrate clusters:</p>
<pre><code class="language-python">from diagrams import Diagram, Cluster
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
with Diagram("Shop Platform"):
with Cluster("Application Tier"):
apps = [
EC2("app-1"),
EC2("app-2")
]
with Cluster("Database Tier"):
primary_db = RDS("primary-db")
replica_db = RDS("read-replica")
apps >> primary_db
primary_db - replica_db
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/e43aacb2-b13b-485c-afee-8c10b10f41d4.png" alt="an example to connect two distinct clusters" style="display: block;" width="600" height="400" loading="lazy">
<p>It's also possible to nest clusters using this syntax:</p>
<pre><code class="language-plaintext">with Cluster("Production"):
with Cluster("Backend"):
with Cluster("API"):
...
</code></pre>
<p>Let's look into a more realistic example that nests clusters:</p>
<pre><code class="language-python">from diagrams import Diagram, Cluster
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
with Diagram("Production Platform"):
with Cluster("Production Region"):
with Cluster("Application Tier"):
with Cluster("API Servers"):
api1 = EC2("api-1")
api2 = EC2("api-2")
database = RDS("main-db")
api1 >> database
api2 >> database
</code></pre>
<p>The example demonstrates a <code>Production Platform</code> that contains a <code>Production Region</code>, which contains an <code>Application Tier</code>. The <code>Application Tier</code> which further groups two API servers. A separate RDS database sits inside the production region, and both API servers connect to it.</p>
<p>Indenting the clusters beneath each other enables nesting of clusters. Refer to the code snippet below:</p>
<pre><code class="language-plaintext">with Cluster("Production Region"):
with Cluster("Application Tier"):
with Cluster("API Servers"):
api1 = EC2("api-1")
api2 = EC2("api-2")
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/1a5215b8-f7a5-42a6-b23b-1d4a58b4121e.png" alt="An example of nesting clusters" style="display: block;" width="600" height="400" loading="lazy">
<h2 id="heading-edges">Edges</h2>
<p>Edges provide another way to connect nodes. Unlike plain <code>>></code> or <code><<</code>, edges let you add properties such as label, color and style.</p>
<p>Previously you used:</p>
<pre><code class="language-python">from diagrams import Diagram
</code></pre>
<p>To use an edge, add:</p>
<pre><code class="language-python">from diagrams import Diagram, Edge
</code></pre>
<p>Now connect <code>app</code> to <code>database</code> using an edge:</p>
<pre><code class="language-python"># Labeling an edge
app >> Edge(label="SQL queries") >> database
</code></pre>
<p>This connects <code>app</code> to <code>database</code> using an edge labeled <code>"SQL queries"</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/bf19d746-9968-463d-ad9a-349bba1feb60.png" alt="Labeling an edge connecting two nodes" style="display: block;" width="600" height="400" loading="lazy">
<p>You can also customize edge color:</p>
<pre><code class="language-python">app >> Edge(label="SQL", color="red") >> database
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/307f97fb-74d5-4313-abf8-548312a8eeef.png" alt="Coloring an edge to red color" style="display: block;" width="600" height="400" loading="lazy">
<p>You can also modify the line style:</p>
<pre><code class="language-plaintext">app >> Edge(style="dashed") >> database
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/d155c30e-4838-4362-ac65-9e6fea4fdcea.png" alt="Using dashed style for connecting two nodes" style="display: block;" width="600" height="400" loading="lazy">
<p>These are some other available edge styles:</p>
<pre><code class="language-plaintext">Edge(style="dotted")
Edge(style="bold")
</code></pre>
<p>The example below combines multiple edge styles:</p>
<pre><code class="language-python">from diagrams import Diagram, Edge
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
with Diagram("Payment Service"):
api = EC2("payment-api")
database = RDS("payments-db")
api >> Edge(
label="payment records",
color="darkgreen",
style="dashed"
) >> database
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/628012e4bc9305ec39363ad6/3be99988-cc1e-4a32-bb88-a743a652225e.png" alt="An example of combining edge style, color and label" style="display: block;" width="600" height="400" loading="lazy">
<p>Note that you can also use edge with <code><<</code> and <code>-</code>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you learned how to create meaningful, consistent diagrams that scale well across teams using the diagram-as-code approach. This allows you to be efficient without compromising on quality and other development or maintenance tasks.</p>
<p>In this tutorial you mostly used AWS components. You can explore other available providers in this <a href="https://diagrams.mingrammer.com/docs/nodes/onprem">guide</a> and create diagrams as per your requirements.</p>
<p>Thank you for reading the article until the end. If gained something from the article, consider sharing it with others.</p>
<p><strong>Stay Connected and Continue Your Learning Journey!</strong></p>
<p><strong>Connect with me on</strong>:</p>
<ul>
<li><p><a href="https://www.linkedin.com/in/zaira-hira/">LinkedIn</a>: I share content related to Linux, Cyber security and DevOps. If you found the article helpful, please leave a recommendation on LinkedIn.</p>
</li>
<li><p><a href="https://twitter.com/hira_zaira">X</a>: I share pre-launch updates and some behind the scenes.</p>
</li>
</ul>
<p>Happy coding!</p>