Docs & Code Guide

Diagram versioning: when a diagram should branch with the code, track latest, or freeze per release

Code has one versioning answer: git. Diagrams have three, because a diagram serves three different readers. The engineer on a feature branch wants the diagram as it will be, the on-call engineer wants it as it is, and the auditor wants it as it was. Each strategy below optimizes for one of those readers, and the tradeoffs are concrete enough to choose in one meeting.

7 min readFor teams deciding where diagram history should live

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

157/20003 credits left
Try:

No account needed · Editable canvas, not a picture

Strategy 1: diagram source in git, next to the code

Committing the diagram source, Mermaid text, draw.io XML, into the repo buys the full git feature set: the diagram branches with the code, a PR that adds a service can update the picture in the same atomic commit, and checking out an old tag shows the diagram as it stood then. For teams already running docs-as-code, this is the default instinct.

The cost is the merge conflict problem, and it is worse than for code. Text-based diagram formats fall into two camps. Hand-positioned formats like draw.io XML store an mxGraphModel full of x and y coordinates, so two people nudging different boxes produce a conflict no human can resolve by reading the diff; the practical fix is treating the file as binary and re-doing one side's change. Generated-layout formats like Mermaid diff cleanly as text, but the rendered layout can reshuffle when unrelated nodes are added, so a reviewer approving the text diff has not necessarily seen what readers will see. The wider tradeoffs of the text-based approach are covered in docs as code for diagrams.

Strategy 2: always-latest, with history in the tool

One canonical diagram lives in the diagramming tool, every README and wiki page embeds it by URL, and edit history stays in the tool rather than in git. This maximizes truth-in-the-moment: there is exactly one current picture, and an edit propagates to every page embedding it. A Datadef diagram embedded via its image URL behaves this way, serving the current render with a five minute cache lifetime so edits appear everywhere within minutes; the embed exists for projects shared public.

The strategy gives up per-branch variants by design: a reader on the v2.3 maintenance branch sees the v3 diagram. For most internal documentation that is the right trade, current beats historically precise. It stops being right the day someone needs the old picture with guarantees, which is the next strategy's job. This is the single source of truth approach applied to diagrams.

Strategy 3: frozen exports per release

Some diagrams must be provable at a point in time. PCI DSS expects a current network diagram and assessors sample historical evidence; SOC 2 change-management reviews ask what the system looked like when a control operated; incident postmortems want the topology at incident time, not at writing time. For all three, a live diagram is the wrong artifact, because live means retroactively different.

The mechanics are one CI job: on every release, export the current diagram to a file named for the tag and commit it to an append-only directory. Frozen copies are exports, deliberately, PNGs that no one can quietly edit, with the live diagram remaining the editable source.

name: freeze-architecture-diagram
on:
  release:
    types: [published]

jobs:
  snapshot:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Save a point-in-time export of the live diagram
        run: |
          mkdir -p docs/architecture/releases
          curl -fsSL -o "docs/architecture/releases/${{ github.event.release.tag_name }}.png" \
            "https://datadef.io/api/embed/my-platform-a1b2c3d4?format=png&width=2400&height=1400"
      - name: Commit the frozen export
        run: |
          git config user.name "release-bot"
          git config user.email "[email protected]"
          git add docs/architecture/releases
          git commit -m "docs: freeze architecture diagram at ${{ github.event.release.tag_name }}"
          git push

Choosing, and the hybrid most teams land on

Choose by who reads old versions. Nobody reads them: always-latest, alone. Engineers read them, diagrams reviewed alongside code on branches: source in git, and accept the merge cost. Auditors read them: always-latest plus frozen exports per release, which is the hybrid most teams end up running because it splits the two jobs cleanly. The live diagram answers what is, the tagged exports answer what was, and neither is asked to fake the other.

The hybrid also dissolves the merge conflict problem for the live half: the canonical diagram is edited in one place rather than merged across branches, and the per-release history accumulates in git where auditors and postmortems can reach it with a checkout.

Decision in one line

History for engineers: diagram source in git. History for nobody: always-latest. History for auditors: always-latest plus a frozen export committed at every release tag.

Keeping the live half actually live

Every strategy above assumes someone updates the diagram when the architecture changes, and that assumption is where diagram versioning usually fails in practice, a perfect version history of a stale diagram is still a stale diagram. The update becomes cheap when an agent can do it: connected to Datadef's MCP server, an agent redraws the diagram from what the repo contains, on request or as a pipeline step after merges. Datadef does not watch the repository itself; the trigger belongs to your process, and when to regenerate is worth deciding explicitly. Schema diagrams have their own version-tracking pattern against migrations, covered in keeping ER diagrams in sync with the database.

FAQ

Should architecture diagrams be version controlled?

Their history should exist somewhere, but git is only one of three valid homes. Diagram source in git suits teams reviewing diagrams alongside code and tolerating merge conflicts on diagram files. An always-latest diagram with history in the tool suits teams that only ever need the current picture. Frozen exports per release suit compliance and postmortems. Many teams run always-latest plus per-release exports.

How do I handle merge conflicts in draw.io files?

Mostly by preventing them: draw.io XML stores coordinates for every element, so concurrent layout edits conflict in ways no one can resolve from the diff. Treat the file as effectively binary, serialize edits through one owner or short-lived branches, and when a conflict lands anyway, take one side wholesale and re-apply the other change in the editor rather than merging XML by hand.

Do auditors need old versions of architecture diagrams?

Often. PCI DSS expects a maintained network diagram and assessors sample evidence across the period; SOC 2 change-management reviews ask what the system looked like when a control operated. A live diagram cannot prove a past state, so the standard pattern is a frozen export, a PNG named for the release tag, committed automatically at each release.

Can one diagram serve multiple code versions or branches?

Not honestly. An always-latest diagram shows every reader the current system, including readers on maintenance branches. If per-version diagrams matter, put the diagram source in git so it branches and tags with the code, or freeze an export per release so each version has its own point-in-time picture.

Where should diagram edit history live if not in git?

In the diagramming tool, provided the published copies embed the diagram by URL rather than pasting exports. The tool holds who changed what and when, the embed keeps every page current, and if you also need immutable milestones, a CI job committing a tagged export at each release adds them without moving the editing workflow.