Featured

How to fix your outdated Azure Diagrams forever

Learn how to automate diagram generation with code, eliminating manual updates once and for all

How to fix your outdated Azure Diagrams forever

"When did we add the Redis cache?"

My teammate pointed at the architecture diagram in our wiki. No Redis. Just App Service talking directly to SQL Database. But I'd deployed the cache layer three weeks ago. It was already handling 60% of our read traffic.

The diagram was a relic. A beautiful, professionally crafted lie.

Sound familiar?

You spend hours in Visio perfecting your Azure architecture. Aligning boxes. Color-coding regions. Getting the arrows just right. You export a PNG. Upload it to your docs. Everyone nods approvingly.

Two sprints later, you've:

  • Added Application Insights
  • Migrated to Managed Identities
  • Introduces a Service Bus queue
  • Switched from blob storage to ADLS Gen2

Your diagram? Still showing the old world. Frozen in time. Mocking you from the documentation site.

You know you should update it. But that means:

  • Finding the original Visio file (or any other tool you created the diagram in)
  • Remembering which version was "final"
  • Spending another hour dragging boxes around
  • Re-exporting and re-uploading
  • Doing this every single sprint

So you don't. Nobody does. And slowly, your documentation becomes fiction.

Here's what clicked for me: your Azure infrastructure changes with every deployment — why are you still drawing diagrams by hand?

The Azure resources you deploy are defined in code. Bicep files. ARM templates. Terraform configs. But somehow, we're still pointing and clicking to document them. It's madness.

I almost gave up on keeping diagrams current. Then I discovered something that changed everything: treating Azure diagrams the same way we treat our infrastructure — as code that lives in git, updates automatically, and stays synchronized with reality.

This isn't about learning another diagramming tool. It's about never having to update an Azure architecture diagram again manually. Sort of, as you'll see.

Let's dive right into it.

The diagram drift problem

Your infrastructure changes every sprint.

New services get deployed. Old endpoints get retired. That microservice architecture you documented in January? It's already evolved. But your diagrams? Still showing the old changes.

This happens everywhere:

  • Sarah uses Visio, version-controlled nowhere
  • Mike prefers draw.io, saves to his Google Drive
  • The team wiki has a PNG someone exported... from what? Nobody knows
  • The actual cloud resources laugh at all of them

You can't win this game. Each tool creates its own silo. Each person has their own "latest version." And when someone asks, "Is this diagram current?" — you have no honest answer.

Here's what nobody admits: most architecture diagrams are outdated the moment you finish drawing them.

The drag-and-drop approach guarantees drift. Because updating diagrams is:

  • Manual (someone has to remember)
  • Tedious (reconnect all those arrows)
  • Invisible (no code review catches stale diagrams)
  • Lonely (not part of a DevOps pipeline)

I spent six months trying to "just be better" at keeping diagrams up to date. Color-coded reminders. Monthly review meetings. Stern emails about documentation hygiene.

All failed, purely because the problem wasn't discipline. It was the approach itself.

What you need before we start

This isn't a fluffy concept piece. We're building a real system. Here's what you need:

  • Python 3.13 or above
  • Node v22.17 or above
  • A code editor (VS Code recommended)
  • Git for version control
  • Go v1.24 or above

The tools we'll be using:

  • D2
  • Diagrams (Python library)
  • Graphviz
  • markdown-link-check

To install the tools:

pip install diagrams

go install oss.terrastruct.com/d2@latest

npm install -g markdown-link-check

Don't skip this. Every tool serves a purpose. D2 gives you declarative syntax. Diagrams handles cloud providers beautifully. Graphviz powers the rendering. And markdown-link-check ensures your docs aren't full of broken links.

Ready? Let's build something that actually works.

The folder structure that changes everything

Before writing a single diagram, get your structure right.

Most people dump everything in a docs folder and hope for the best. That's how you end up with diagram-final-v3-ACTUAL.png. Instead, organize by intent:

project-root/
├── diagrams/
│   ├── d2/
│   │   ├── azure-devops-pool.d2
│   │   └── network-topology.d2
│   ├── graphviz/
│   │   ├── aws-infrastructure.py
│   │   └── azure-web-app.py
├── docs/
|   ├── .assets/
│   ├── architecture/
│   │   ├── overview.md
│   │   └── deployment-guide.md
│   ├── explanation/
│   └── reference/
├── build.ps1
└── README.md

So why does the above matter?

The diagrams/ folder separates your source (.d2, .py) from .assets folder in the docs. This is a critical piece. Your source code (diagram as code) lives in version control. Your generated images get referenced in Markdown.

The docs/ folder follows the Diataxis framework — organizing by reader intent (tutorials, how-to, reference, explanation). This makes documentation discoverable.

The build.ps1 script (we'll create this) generates all diagrams and validates all links. One command. Everything updated.

Your markdown files reference diagrams using relative paths:

![Azure DevOps Pool](../.assets/azure-devops-pool.png)

When the build script regenerates diagrams, your docs automatically show the latest version. No manual image updates. No version confusion. Just truth.

Why Diagrams as Code changes the "Game"

Here's what clicked for me: diagrams are infrastructure documentation, and infrastructure is code.

If you define your cloud resources in Terraform or Bicep — why are you drawing diagrams by hand?

Diagrams as code means:

Version control that actually works

git diff diagrams/python/aws-infrastructure.py

Shows exactly what changed in your architecture. Not a binary blob that git can't parse. Actual, reviewable changes. Your team sees: "Added Redis cache between API and database."

Pull requests become architecture reviews.

Automated generation

Your DevOps pipeline can regenerate diagrams on every commit. Or you run it locally. Deploy a new service? The diagram updates automatically. No human remembers to update it. No diagram drift.

Single source of truth

The .d2 file or .py file IS the source. Everything else is generated. No more "which version is correct?" There's only one version.

Consistency by default

Use the same cloud provider icons. The same styling. The same layout engine. Every diagram looks professional without you having to think about it.

Building your first D2 diagram

D2 is beautifully simple. Declarative syntax that reads like plain English:

# diagrams/d2/simple-api.d2

client: Mobile App {
  shape: rectangle
}

api: API Gateway {
  shape: hexagon
}

database: PostgreSQL {
  shape: cylinder
}

cache: Redis {
  shape: stored_data
}

# Define connections
client -> api: HTTPS
api -> cache: Check cache
api -> database: Query data
cache -> database: Cache miss

Then, to generate it, open your terminal and run:

d2 diagrams/d2/simple-api.d2 docs/.assets/simple-api.png

That's it. You now have a diagram that:

  • Lives in version control
  • Can be reviewed in pull requests
  • Regenerates consistently
  • Documents your actual architecture

Want to add a message queue? Edit the .d2 file:

queue: Message Queue {
  shape: queue
}

api -> queue: Publish events
worker -> queue: Consume events

Run the command again. Diagram updated. Commit the change. Your team can see exactly what changed during the architecture review.

Python diagrams for cloud architecture

For cloud-specific diagrams, Python's diagrams library is exceptional:

# azure-web-app.py

from diagrams import Diagram, Cluster
from diagrams.azure.compute import AppServices, FunctionApps
from diagrams.azure.database import SQLDatabases
from diagrams.azure.network import LoadBalancers
from diagrams.azure.storage import BlobStorage

with Diagram("Azure Web App", 
             filename="docs/.assets/azure-web-app",
             show=False):
    
    lb = LoadBalancers("Load Balancer")
    
    with Cluster("Web Tier"):
        web = AppServices("Web App")
    
    with Cluster("Data Tier"):
        db = SQLDatabases("SQL Database")
        storage = BlobStorage("Blob Storage")
    
    lb >> web >> db
    web >> storage

To run it, call python:

python .diagrams/python/azure-web-app.py

Boom. Professional cloud architecture diagram using official Azure icons. The Cluster grouping shows logical tiers. The connections show data flow. And it's all defined in 20 lines of Python.

The build script that ties it all together

Here's where magic happens. A single script that:

  1. Generate all D2 diagrams
  2. Execute all Python diagrams
  3. Validates all Markdown links
  4. Reports any problems

Create build.ps1:

<#
.SYNOPSIS
    Build and validate all diagrams and documentation
.DESCRIPTION
    Generates diagrams from D2 and Python sources,
    then validates all markdown links
#>

param(
    [switch]$Build,
    [switch]$ValidateLinks,
    [switch]$Verbose
)

$ErrorActionPreference = 'Stop'

$assetPath = Join-Path -Path "docs" -ChildPath ".assets"

function Write-Success { Write-Host $args -ForegroundColor Green }
function Write-Info { Write-Host $args -ForegroundColor Cyan }
function Write-Failure { Write-Host $args -ForegroundColor Red }

if ($Build) {
    Write-Info "=== Building Diagrams ==="
    
    # Generate D2 diagrams
    Write-Info "Generating D2 diagrams..."
    Get-ChildItem -Path ".diagrams/d2" -Filter "*.d2" | ForEach-Object {
        $outputName = $_.BaseName
        $outputPath = Join-Path $assetPath "$outputName.png"
        
        Write-Info "  Processing: $($_.Name) to $outputPath"
        d2 $_.FullName $outputPath
        
        if ($LASTEXITCODE -eq 0) {
            Write-Success "  ✓ Generated: $outputPath"
        } else {
            Write-Failure "  ✗ Failed: $($_.Name)"
        }
    }
    
    Write-Info "Generating Python diagrams..."
    Get-ChildItem -Path ".diagrams/graphviz" -Filter "*.py" | ForEach-Object {
        Write-Info "  Processing: $($_.Name)"
        python3 $_.FullName
        
        if ($LASTEXITCODE -eq 0) {
            Write-Success "  ✓ Generated from: $($_.Name)"
        } else {
            Write-Failure "  ✗ Failed: $($_.Name)"
        }
    }
}

if ($ValidateLinks) {
    Write-Info "=== Validating Documentation Links ==="
    
    Get-ChildItem -Path "docs" -Filter "*.md" -Recurse | ForEach-Object {
        Write-Info "  Checking: $($_.FullName)"
        $result = markdown-link-check $_.FullName --quiet
        
        if ($result) 
        {
            $result | ForEach-Object {Write-Failure "  ✗ $_" }
        }
    }
    
    Write-Success "Link validation complete!"
}

Now your workflow becomes:

./build.ps1 -Build -ValidateLinks
Figure 1: Producing images from build script

This system shines the most when it's part of:

  • Git pre-commit hook
  • DevOps pipeline
  • Documentation lives in Markdown

What changes when you do this

I just implemented this and here's what happened:

Our architecture diagrams are always current. Every deployment updates them. No manual intervention required.

Pull requests include architecture changes. Teams discuss the `.d2` changes alongside the code. Better reviews. Fewer surprises.

New developers trust the docs. They know diagrams reflect reality because they're generated from the same repo as the infrastructure.

We stopped having "which version is right?" conversations. There's one version. In git. Generated automatically.

And here's the surprising part: we create more diagrams now. Because it's easy. Add 15 lines to a .d2 file, run the build script, done. No hunting for the right tool. No fighting with connector arrows. Just describe what exists, generate, and commit. Or if you aren't sure, ask Copilot to develop one for you.

Your turn

Stop letting your diagrams lie to you.

Set up the folder structure today. Create one D2 diagram. One Python diagram. Build the automation script. Commit everything to git.

Then watch what happens when your team sees diagrams that actually stay current.

The best ROI of your time? It's in this automation. Because every hour you spend setting it up saves hundreds of hours of manual updates, confusion, and deployment mistakes caused by outdated diagrams.

Your infrastructure changes constantly. Your diagrams should too.

Make them code. Make them automatic. Make them trustworthy.

If you want to learn more, check out the following references: