Jesse A. Green / notebook

Diagrams User Guide — Diagram as Code

Generated June 2026 from mingrammer/diagrams at 23f06b4

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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). Pass show=False while 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 install and forget — it is a system program that provides the dot command. 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 pipx if you intend to import provider nodes in your own scripts. Because the Diagrams CLI simply executes your file in its own environment, a pipx-isolated install can lead to ModuleNotFoundError: No module named 'diagrams.cli' or missing provider modules. Use pip, pipenv, poetry, or uv into 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 inside with 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

Group is an alias for Cluster — from diagrams import Group works 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 the Diagram. Note that concentrate only works with the spline curve style (not ortho) and the default dot layout 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 theme and autolabel options are recent additions to the library. If you installed an older release from PyPI and these keywords raise TypeError, upgrade with pip 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.