Diagrams in CI/CD — Validation, Linting & Auto-Publishing in 2026
Storing diagrams as text files in Git is only the first step. Once they live alongside code, the same CI/CD discipline you apply to source — linting, validation, automated builds, deploy gates — should apply to diagrams too. Otherwise the most common failure mode is broken Mermaid blocks shipped straight into production docs because nobody previewed them.
The Six Stages of a Diagram Pipeline
A mature diagram pipeline mirrors a code pipeline: catch problems early, block bad changes from merging, build artifacts deterministically, and watch for drift over time. Here is the full picture:
| Pipeline Stage | Purpose | Common Tools |
|---|---|---|
| Pre-commit | Catch syntax errors before push | lefthook, pre-commit, mermaid-cli |
| PR Validation | Block bad diagrams from merging | GitHub Actions, GitLab CI |
| PR Preview | Render diff visualizations in PRs | reg-suit, BackstopJS, custom bots |
| Build | Render .mmd / .puml → SVG artifacts | mermaid-cli, plantuml-jar, kroki |
| Publish | Deploy to docs site / CDN | Netlify, Vercel, Pages |
| Drift Watch | Alert when source ↔ diagram diverge | Custom workflows, scheduled jobs |
Stage 1 — Pre-commit Syntax Checks
The cheapest place to catch a malformed Mermaid block is the developer's laptop, before the commit ever reaches CI. lefthook orpre-commit can run mmdc (Mermaid CLI) against staged.mmd files and reject the commit if any fail to parse.
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: mermaid-validate
name: Validate Mermaid diagrams
entry: bash -c 'for f in "$@"; do mmdc -i "$f" -o /tmp/out.svg --quiet; done' --
language: system
files: \.mmd$The same trick works for PlantUML (plantuml -checkonly) and D2 (d2 fmt --check).
Stage 2 — PR Validation in GitHub Actions
Pre-commit only catches contributors who installed the hook. CI is the enforcement point. A small workflow that runs on every PR can validate every diagram in the repo and block merge on failure:
# .github/workflows/diagrams.yml
name: Diagrams
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm i -g @mermaid-js/mermaid-cli
- name: Validate all .mmd files
run: |
find docs -name '*.mmd' -print0 | \
xargs -0 -I{} mmdc -i {} -o /tmp/out.svg --quietAdd a matrixover diagram types (Mermaid, PlantUML, D2) if your repo mixes formats. Cache the renderer's npm install for faster runs.
Stage 3 — Visual PR Previews
Reviewers should not have to mentally compile Mermaid in a code review. Two patterns fix this:
- Auto-comment with rendered images. A workflow renders changed diagrams to PNG and posts them as a sticky PR comment. Reviewers see before/after without leaving GitHub.
- Visual regression diffs. Tools like
reg-suitcompare rendered diagrams to a baseline and flag pixel diffs above a threshold. Useful for generated architecture diagrams where any change is significant.
GitHub natively renders Mermaid in Markdown, so the simplest preview is just to keep diagrams inline in .md files — the diff view shows them rendered.
Stage 4 — Build: Source → SVG
Most docs sites consume SVG, not the source DSL. Generate SVGs as a build step so the published output is deterministic and review-able. Three popular renderers:
- Mermaid CLI (
mmdc) — npm-installable, runs Puppeteer under the hood. Slow first run, fine in CI with caching. - PlantUML JAR —
java -jar plantuml.jar -tsvg src/. Fast, no JS runtime needed; pin the JAR version for reproducible builds. - Kroki — a single HTTP service that renders Mermaid, PlantUML, D2, BPMN, and many more. Self-host or hit kroki.io. Great when you have many formats.
# Build all diagrams in docs/diagrams/ → docs/static/
- name: Render diagrams
run: |
mkdir -p docs/static/diagrams
for f in docs/diagrams/*.mmd; do
out=docs/static/diagrams/$(basename "${f%.mmd}").svg
mmdc -i "$f" -o "$out" --quiet
doneStage 5 — Auto-Publishing
Once SVGs are generated, publishing them is the same as publishing any static asset. Two patterns work well:
- Commit-back to main — a scheduled workflow generates SVGs and commits them to a
generated/directory. Simple, but pollutes Git history. - Build-time generation — your docs site (Docusaurus, MkDocs, Astro) generates SVGs during its build and never commits them. Cleaner, but the docs build takes longer.
We recommend build-time generation for active repos and commit-back for low-frequency archival diagrams.
Stage 6 — Drift Detection
The hardest accessibility-of-truth problem with diagrams is drift: code changes, diagrams do not. A scheduled job can compare an auto-generated diagram (from dependency analysis, OpenAPI spec, Terraform graph) against the committed version and open an issue when they diverge.
# Weekly drift check
on:
schedule:
- cron: '0 9 * * 1' # Monday 09:00 UTC
jobs:
drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/regenerate-arch-diagram.sh > /tmp/new.mmd
- name: Compare
run: |
if ! diff -q docs/diagrams/architecture.mmd /tmp/new.mmd; then
gh issue create \
--title "Architecture diagram drift detected" \
--body "Re-generate \`architecture.mmd\` — see workflow run."
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}Format Choices Affect Pipeline Complexity
Text-based formats (Mermaid, PlantUML, D2) drop straight into this pipeline. Binary-ish formats (Excalidraw JSON, Draw.io XML) work too, but linting is harder and visual diffs require rendering both versions. If you receive Excalidraw or Draw.io files from collaborators and want to bring them into a text-based CI pipeline, convert them once with Orriguii Diagram Converter and commit the resulting.mmd or .svg as the source of truth.