Diagrams lets you draw cloud system architecture in Python code. You write a short Python script that declares nodes (services like EC2, RDS, a Kubernetes Pod) and connects them with arrows, and Diagrams renders a polished architecture image. It was built for prototyping a new system design without reaching for a drawing tool, and because your diagram is code, you can keep it under version control and review changes the same way you review everything else.
Note
Diagrams does not create, control, or provision any real cloud resources, and it does not generate CloudFormation or Terraform. It draws pictures. Think of it as a programmable whiteboard for system architecture.
This guide covers everything you need as a user: installation, the four building blocks (Diagram, Node, Cluster, Edge), the connection operators, ready-to-run recipes for AWS / GCP / Kubernetes / on-premises stacks, the full catalog of providers, and troubleshooting for the errors you are most likely to hit.
1. Quick Start
Once Diagrams and Graphviz are installed (see Installation), create a file named diagram.py:
# diagram.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("Web Service", show=False):
ELB("lb") >> EC2("web") >> RDS("userdb")
Run it like any Python script:
$ python diagram.py
$ ls
diagram.py web_service.png
You now have web_service.png in your working directory: a load balancer pointing to a web server pointing to a database. That is the whole idea — declare components, connect them with arrows, run the file.
Tip
The first argument to
Diagram(...)("Web Service") becomes both the image title and the output filename (lowercased, spaces replaced with underscores →web_service.png). Passshow=Falsewhile you iterate so the image doesn't pop open in your viewer on every run.
2. Installation
Installing Diagrams is a two-step process. Diagrams is a Python package, but the actual rendering is done by Graphviz, a separate native program. You must install Graphviz first.
Requirements
| Requirement | Version | Notes |
|---|---|---|
| Python | 3.9 or higher | Check with python --version. |
| Graphviz | Any recent release | The rendering engine. Must be on your PATH. |
Important
Graphviz is not a Python library you can
pip installand forget — it is a system program that provides thedotcommand. If it is missing, Diagrams runs but rendering fails. Install it first.
Step 1 — Install Graphviz
macOS (Homebrew)
brew install graphviz
Windows (Chocolatey)
choco install graphviz
Windows (Winget)
winget install Graphviz.Graphviz -i
Debian / Ubuntu
sudo apt-get install graphviz
Confirm it is on your PATH:
$ dot -V
dot - graphviz version 9.0.0 (20230911.1827)
Step 2 — Install Diagrams
pip
pip install diagrams
pipenv
pipenv install diagrams
poetry
poetry add diagrams
uv
uv tool install diagrams
Verify the install from Python:
$ python -c "import diagrams; print('ok')"
ok
Warning
Avoid installing Diagrams with
pipxif you intend toimportprovider nodes in your own scripts. Because the Diagrams CLI simply executes your file in its own environment, apipx-isolated install can lead toModuleNotFoundError: No module named 'diagrams.cli'or missing provider modules. Usepip,pipenv,poetry, oruvinto the environment your script runs in. See Troubleshooting.
3. Core Concepts
A Diagrams script is built from exactly four objects. Learn these and you can draw anything.
| Object | What it is | Used as |
|---|---|---|
Diagram |
The whole canvas / drawing context. | A with block. Everything lives inside it. |
Node |
A single component (a service, server, queue…). | Created by importing a provider class, e.g. EC2("web"). |
Cluster |
A labeled box that groups related nodes. | A nested with block. |
Edge |
A connection between nodes, with optional label/color/style. | Placed between nodes with the connection operators. |
Every node and cluster must be created inside a with Diagram(...) block. Diagrams uses an implicit global context, so you never pass the diagram around by hand.
Caution
Creating a node or cluster outside a diagram context raises
EnvironmentError: Global diagrams context not set up. Always nest your nodes insidewith Diagram(...).
The Diagram context
from diagrams import Diagram
from diagrams.aws.compute import EC2
with Diagram("Simple Diagram"):
EC2("web")
Running this writes simple_diagram.png and (by default) opens it.
Nodes: provider, type, name
A node has three parts: a provider (aws), a resource type (compute), and a name you give it ("web"):
from diagrams.aws.compute import EC2
EC2("web") # provider=aws, type=compute, class=EC2, label="web"
You import the class from diagrams.<provider>.<category> and call it with a label. The full provider list is in section 7.
4. Connecting Nodes — The Operators
Data flow is expressed with three operators. This is the heart of the Diagrams syntax.
| Operator | Direction | Meaning |
|---|---|---|
>> |
Left → right | Points an arrow forward from the left node to the right node. |
<< |
Right → left | Points an arrow backward. |
- |
None | Connects two nodes with an undirected line (no arrowhead). |
from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB
from diagrams.aws.storage import S3
with Diagram("Web Services", show=False):
ELB("lb") >> EC2("web") >> RDS("userdb") >> S3("store")
ELB("lb") >> EC2("web") >> RDS("userdb") << EC2("stat")
(ELB("lb") >> EC2("web")) - EC2("web") >> RDS("userdb")
Warning
When you mix
-with>>/<<on the same line, Python's operator precedence can produce surprising results. Wrap the directed part in parentheses, as in(ELB("lb") >> EC2("web")) - EC2("web")above.
Note
The rendered order is the reverse of declaration order. If layout looks flipped from what you expected, this is why.
Grouping nodes with lists
Connecting many nodes one-by-one is noisy. Put nodes in a Python list to fan-out or fan-in to all of them at once:
from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB
with Diagram("Grouped Workers", show=False, direction="TB"):
ELB("lb") >> [EC2("worker1"),
EC2("worker2"),
EC2("worker3"),
EC2("worker4"),
EC2("worker5")] >> RDS("events")
Caution
You cannot connect two lists directly (
[a, b] >> [c, d]) — Python does not allow shift operations between two lists. Connect a single node to a list, or a list to a single node.
5. Clusters — Grouping Components
A Cluster draws a labeled, shaded box around a set of nodes. Use it for a database tier, a service group, a VPC, or anything you'd circle on a whiteboard.
from diagrams import Cluster, Diagram
from diagrams.aws.compute import ECS
from diagrams.aws.database import RDS
from diagrams.aws.network import Route53
with Diagram("Simple Web Service with DB Cluster", show=False):
dns = Route53("dns")
web = ECS("service")
with Cluster("DB Cluster"):
db_primary = RDS("primary")
db_primary - [RDS("replica1"),
RDS("replica2")]
dns >> web >> db_primary
Tip
Groupis an alias forCluster—from diagrams import Groupworks identically. Use whichever name reads better in your script.
Nested clusters
Clusters nest to any depth. Each nesting level gets a slightly different background shade automatically, so the hierarchy is visible:
from diagrams import Cluster, Diagram
from diagrams.aws.compute import ECS, EKS, Lambda
from diagrams.aws.database import Redshift
from diagrams.aws.integration import SQS
from diagrams.aws.storage import S3
with Diagram("Event Processing", show=False):
source = EKS("k8s source")
with Cluster("Event Flows"):
with Cluster("Event Workers"):
workers = [ECS("worker1"),
ECS("worker2"),
ECS("worker3")]
queue = SQS("event queue")
with Cluster("Processing"):
handlers = [Lambda("proc1"),
Lambda("proc2"),
Lambda("proc3")]
store = S3("events store")
dw = Redshift("analytics")
source >> workers >> queue >> handlers
handlers >> store
handlers >> dw
Note
There is no depth limit to nesting clusters.
6. Edges — Labels, Colors, and Styles
When you need an arrow to carry meaning — a label, a color, a dashed line — use the Edge object between two nodes. An Edge supports three high-level attributes: label, color, and style (plus any raw Graphviz edge attribute).
from diagrams import Cluster, Diagram, Edge
from diagrams.onprem.analytics import Spark
from diagrams.onprem.compute import Server
from diagrams.onprem.database import PostgreSQL
from diagrams.onprem.inmemory import Redis
from diagrams.onprem.aggregator import Fluentd
from diagrams.onprem.monitoring import Grafana, Prometheus
from diagrams.onprem.network import Nginx
from diagrams.onprem.queue import Kafka
with Diagram("Advanced Web Service (colored)", show=False):
ingress = Nginx("ingress")
metrics = Prometheus("metric")
metrics << Edge(color="firebrick", style="dashed") << Grafana("monitoring")
with Cluster("Service Cluster"):
grpcsvc = [Server("grpc1"), Server("grpc2"), Server("grpc3")]
with Cluster("Database HA"):
primary = PostgreSQL("users")
primary - Edge(color="brown", style="dotted") - PostgreSQL("replica") << Edge(label="collect") << metrics
grpcsvc >> Edge(color="black") >> primary
aggregator = Fluentd("logging")
aggregator >> Edge(label="parse") >> Kafka("stream") >> Edge(color="black", style="bold") >> Spark("analytics")
ingress >> Edge(color="darkgreen") << grpcsvc >> Edge(color="darkorange") >> aggregator
Edge attribute |
Type | Example |
|---|---|---|
label |
str | Edge(label="collect") |
color |
str (color name or hex) | Edge(color="firebrick") |
style |
str (solid, dashed, dotted, bold) |
Edge(style="dashed") |
forward / reverse |
bool | arrowhead direction (usually set via >> / <<) |
**attrs |
any Graphviz edge attribute | Edge(minlen="2", headport="w") |
Tip
For dense diagrams, reduce visual clutter with the "merged edges" trick: set
graph_attr={"concentrate": "true", "splines": "spline"}on theDiagram. Note thatconcentrateonly works with thesplinecurve style (notortho) and the defaultdotlayout engine.
7. Provider Catalog
Diagrams ships 20 cloud/infrastructure providers plus three special-purpose modules. Each provider groups its nodes into resource-type categories, and you import a node from diagrams.<provider>.<category>.
| Provider | Import root | Categories | Use it for |
|---|---|---|---|
| AWS | diagrams.aws |
26 (compute, database, network, storage, security, analytics, integration, ml, …) | Amazon Web Services |
| Azure | diagrams.azure |
32 | Microsoft Azure |
| GCP | diagrams.gcp |
13 (compute, database, analytics, ml, network, storage, …) | Google Cloud Platform |
| Kubernetes | diagrams.k8s |
12 (compute, network, storage, rbac, controlplane, …) | Kubernetes objects |
| Alibaba Cloud | diagrams.alibabacloud |
10 | Alibaba Cloud |
| Oracle Cloud | diagrams.oci |
9 | Oracle Cloud Infrastructure |
| IBM | diagrams.ibm |
14 | IBM Cloud |
| Firebase | diagrams.firebase |
5 | Firebase services |
| DigitalOcean | diagrams.digitalocean |
4 | DigitalOcean |
| Elastic | diagrams.elastic |
8 | Elastic / ELK stack |
| OpenStack | diagrams.openstack |
22 | OpenStack |
| Outscale | diagrams.outscale |
4 | Outscale |
| On-Premises | diagrams.onprem |
31 (compute, database, network, monitoring, ci, queue, …) | Self-hosted / open-source stacks |
| SaaS | diagrams.saas |
15 (cdn, chat, identity, logging, …) | SaaS products |
| Programming | diagrams.programming |
4 (language, framework, flowchart, runtime) | Languages & frameworks |
| GIS | diagrams.gis |
17 | Geographic information systems |
| Generic | diagrams.generic |
9 (os, device, network, storage, …) | Vendor-neutral shapes |
Plus three special modules:
diagrams.custom— bring your own icon (see section 8).diagrams.c4— C4-model software-architecture diagrams (see section 8).diagrams.base— internal base nodes (you normally won't import these directly).
Note
There are roughly 2,400 node classes across all providers — far too many to list here. The authoritative, browsable index of every available node (with its icon) lives at diagrams.mingrammer.com under the per-provider node pages (for example, the AWS node list). Import paths always follow
diagrams.<provider>.<category>.
Finding the right class name
Many long service names have short aliases. For example, in AWS:
| Full class | Alias |
|---|---|
SimpleStorageServiceS3 |
S3 |
ElasticContainerService |
ECS |
ElasticKubernetesService |
EKS |
ElasticLoadBalancing |
ELB |
KeyManagementService |
KMS |
SimpleQueueServiceSqs |
SQS |
Both the full name and the alias work. Common aliases exist across all major providers (e.g. GKE for Google's Kubernetes Engine, PVC for a Kubernetes PersistentVolumeClaim).
8. Recipes
Copy-paste these and adapt the node names. All use show=False so they save quietly.
Clustered web service (AWS)
from diagrams import Cluster, Diagram
from diagrams.aws.compute import ECS
from diagrams.aws.database import ElastiCache, RDS
from diagrams.aws.network import ELB, Route53
with Diagram("Clustered Web Services", show=False):
dns = Route53("dns")
lb = ELB("lb")
with Cluster("Services"):
svc_group = [ECS("web1"), ECS("web2"), ECS("web3")]
with Cluster("DB Cluster"):
db_primary = RDS("userdb")
db_primary - [RDS("userdb ro")]
memcached = ElastiCache("memcached")
dns >> lb >> svc_group
svc_group >> db_primary
svc_group >> memcached
Message-collecting pipeline (GCP)
from diagrams import Cluster, Diagram
from diagrams.gcp.analytics import BigQuery, Dataflow, PubSub
from diagrams.gcp.compute import AppEngine, Functions
from diagrams.gcp.database import BigTable
from diagrams.gcp.iot import IotCore
from diagrams.gcp.storage import GCS
with Diagram("Message Collecting", show=False):
pubsub = PubSub("pubsub")
with Cluster("Source of Data"):
[IotCore("core1"), IotCore("core2"), IotCore("core3")] >> pubsub
with Cluster("Targets"):
with Cluster("Data Flow"):
flow = Dataflow("data flow")
with Cluster("Data Lake"):
flow >> [BigQuery("bq"), GCS("storage")]
with Cluster("Event Driven"):
with Cluster("Processing"):
flow >> AppEngine("engine") >> BigTable("bigtable")
with Cluster("Serverless"):
flow >> Functions("func") >> AppEngine("appengine")
pubsub >> flow
Exposed pod with 3 replicas (Kubernetes)
from diagrams import Diagram
from diagrams.k8s.clusterconfig import HPA
from diagrams.k8s.compute import Deployment, Pod, ReplicaSet
from diagrams.k8s.network import Ingress, Service
with Diagram("Exposed Pod with 3 Replicas", show=False):
net = Ingress("domain.com") >> Service("svc")
net >> [Pod("pod1"),
Pod("pod2"),
Pod("pod3")] << ReplicaSet("rs") << Deployment("dp") << HPA("hpa")
Programmatic node generation (Kubernetes, with a loop)
You can use ordinary Python — loops, comprehensions, variables — to build diagrams:
from diagrams import Cluster, Diagram
from diagrams.k8s.compute import Pod, StatefulSet
from diagrams.k8s.network import Service
from diagrams.k8s.storage import PV, PVC, StorageClass
with Diagram("Stateful Architecture", show=False):
with Cluster("Apps"):
svc = Service("svc")
sts = StatefulSet("sts")
apps = []
for _ in range(3):
pod = Pod("pod")
pvc = PVC("pvc")
pod - sts - pvc
apps.append(svc >> pod >> pvc)
apps << PV("pv") << StorageClass("sc")
Tip
Because diagrams are just Python, you can drive node creation from a config file, an inventory list, or a Terraform state export — anything you can turn into a list.
Custom nodes (your own icon)
The Custom node lets you use any image as a node icon — useful for a product Diagrams doesn't ship an icon for:
from urllib.request import urlretrieve
from diagrams import Cluster, Diagram
from diagrams.aws.database import Aurora
from diagrams.custom import Custom
from diagrams.k8s.compute import Pod
rabbitmq_url = "https://jpadilla.github.io/rabbitmqapp/assets/img/icon.png"
rabbitmq_icon = "rabbitmq.png"
urlretrieve(rabbitmq_url, rabbitmq_icon)
with Diagram("Broker Consumers", show=False):
with Cluster("Consumers"):
consumers = [Pod("worker"), Pod("worker"), Pod("worker")]
queue = Custom("Message queue", rabbitmq_icon)
queue >> consumers >> Aurora("Database")
Custom(label, icon_path) takes the path to a local image file. Download or place the icon next to your script first.
C4-model diagrams
The diagrams.c4 module draws C4-model software architecture diagrams (Person / System / Container / Database) instead of vendor icons:
from diagrams import Diagram
from diagrams.c4 import Person, Container, Database, System, SystemBoundary, Relationship
with Diagram("C4 Diagram", show=False):
customer = Person("Customer", "A user of the system")
with SystemBoundary("Internet Banking System"):
web = Container("Web App", "Python/Flask", "Delivers the web pages")
api = Container("API App", "Python/FastAPI", "Provides banking functionality")
db = Database("Database", "PostgreSQL", "Stores user accounts")
mainframe = System("Mainframe Banking System", external=True)
customer >> Relationship("Uses") >> web
web >> Relationship("Makes API calls to") >> api
api >> Relationship("Reads/writes") >> db
api >> Relationship("Uses") >> mainframe
C4 helpers: Person(name, description, external=False), System(name, description, external=False), Container(name, technology, description), Database(name, technology, description), SystemBoundary(name), and Relationship(label).
9. The diagrams CLI
Installing the package also installs a diagrams command. It runs one or more diagram files in sequence — handy in CI or a Makefile:
$ diagrams diagram1.py diagram2.py
| Argument | Required | Description |
|---|---|---|
path (one or more) |
Yes | A Python file containing diagrams code. |
Note
The CLI simply executes each file you pass, exactly as
python <file>would. It has no flags beyond the file paths. It is a convenience for batch-rendering several diagram scripts at once.
10. Diagram Options Reference
All options are keyword arguments to Diagram(...).
| Option | Default | Allowed values | Purpose |
|---|---|---|---|
name |
"" |
any string | Title shown on the image; also the default filename. |
filename |
derived from name |
any string (no extension) | Output filename without extension. |
direction |
"LR" |
TB, BT, LR, RL |
Data-flow direction (top-bottom, bottom-top, left-right, right-left). |
outformat |
"png" |
png, jpg, svg, pdf, dot (or a list) |
Output format(s). Pass a list to emit several at once. |
curvestyle |
"spline" |
ortho, curved, spline, polyline |
Edge routing style. |
show |
True |
True / False |
Open the image after rendering. |
strict |
False |
True / False |
Merge duplicate (multi-) edges. |
autolabel |
False |
True / False |
Prefix each node label with its class name. |
theme |
"neutral" |
neutral, pastel, blues, greens, orange |
Color theme for clusters and edges. |
graph_attr |
{} |
dict | Raw Graphviz graph attributes. |
node_attr |
{} |
dict | Raw Graphviz node attributes. |
edge_attr |
{} |
dict | Raw Graphviz edge attributes. |
Multiple output formats at once
from diagrams import Diagram
from diagrams.aws.compute import EC2
with Diagram("Multi Output", outformat=["jpg", "png", "dot"], show=False):
EC2("web")
Choosing a color theme
from diagrams import Cluster, Diagram
from diagrams.aws.compute import EC2
with Diagram("Themed", theme="blues", show=False):
with Cluster("Tier 1"):
EC2("web")
Note
The
themeandautolabeloptions are recent additions to the library. If you installed an older release from PyPI and these keywords raiseTypeError, upgrade withpip install -U diagrams.
Rendering inside Jupyter
Bind the diagram to a variable and reference it as the last cell expression to render inline:
from diagrams import Diagram
from diagrams.aws.compute import EC2
with Diagram("Simple Diagram") as diag:
EC2("web")
diag
11. Troubleshooting
Error: Global diagrams context not set up
EnvironmentError: Global diagrams context not set up
Cause: You created a Node (e.g. EC2("web")) or a Cluster outside of a with Diagram(...) block.
Solution: Move all node and cluster creation inside the diagram context.
Prevention: Treat with Diagram(...) as mandatory — nothing diagrams-related happens outside it.
Error: "<value>" is not a valid theme
ValueError: "dark" is not a valid theme. Choose from: neutral, pastel, blues, greens, orange
Cause: An unsupported value passed to theme=.
Solution: Use one of neutral, pastel, blues, greens, orange.
Error: "<value>" is not a valid direction / curvestyle / output format
Cause: An out-of-range value for direction, curvestyle, or outformat.
Solution: Use an allowed value from the options table: direction ∈ {TB, BT, LR, RL}; curvestyle ∈ {ortho, curved, spline, polyline}; outformat ∈ {png, jpg, svg, pdf, dot}.
Error: ModuleNotFoundError: No module named 'diagrams.cli'
Cause: Diagrams was installed in an isolated environment (commonly via pipx) that doesn't match the environment your script imports from.
Solution: Reinstall with pip install diagrams (or pipenv/poetry/uv) into the same environment you run your script in.
Prevention: Don't use pipx for a library you intend to import.
Error: graphviz ExecutableNotFound / nothing renders
graphviz.backend.ExecutableNotFound: failed to execute 'dot'
Cause: Graphviz (the native program) isn't installed or isn't on your PATH.
Solution: Install Graphviz (see Step 1) and confirm dot -V works in the same shell.
Edges look broken or float off the nodes on dense diagrams
Cause: With many nodes, the ortho/spline routing in the underlying Graphviz engine can produce broken or detached edges (a known Graphviz limitation).
Solution: Try a different curvestyle (e.g. "curved" or "polyline"), reduce edge density using clusters and the "merged edges" trick (graph_attr={"concentrate": "true", "splines": "spline"}), or split a large diagram into several smaller ones.
Icons / logos don't appear
Cause: Certain releases shipped with broken icon paths.
Solution: Upgrade to the latest version (pip install -U diagrams). Verify icons appear on a tiny one-node diagram before debugging a large one.
Mixing - and >> gives unexpected connections
Cause: Python operator precedence resolves arithmetic (-) and shift (>>/<<) operators in an order you didn't intend.
Solution: Add parentheses around the directed segment: (a >> b) - c.
12. FAQ
Q: Does Diagrams provision real cloud resources? No. It only draws pictures. It does not create infrastructure and does not generate CloudFormation or Terraform.
Q: What image formats can I export?
png (default), jpg, svg, pdf, and dot. Pass a list to outformat to produce several at once.
Q: Why didn't an image open after I ran my script?
Either you passed show=False, or your OS image viewer isn't configured. The file is still written to disk — check your working directory for <name>.png.
Q: How do I control where the file is saved and what it's named?
Use filename="path/to/name" (no extension). The extension is determined by outformat.
Q: Can I use an icon for a service Diagrams doesn't include?
Yes — use from diagrams.custom import Custom and pass a path to your own image: Custom("My Service", "myicon.png").
Q: How do I find the exact class name for a service?
Browse the per-provider node index at diagrams.mingrammer.com. Import path is always diagrams.<provider>.<category>.
Q: Can I generate diagrams in CI without a display?
Yes. Set show=False so no viewer is launched, and run python diagram.py (or diagrams diagram.py) as a build step. You only need Graphviz installed on the runner.
Q: Can I group nodes into a labeled box?
Yes — that's a Cluster (aliased as Group). Clusters can be nested to any depth.
Q: Which Python version do I need? Python 3.9 or higher.
Q: How do I change the layout direction?
Pass direction="TB" (top-to-bottom), "BT", "LR" (default), or "RL" to Diagram(...).
Q: Can I keep my diagrams in version control?
That's the whole point of "Diagram as Code." Commit your .py files; regenerate images on demand or in CI.