Skip to content

Instantly share code, notes, and snippets.

@andynu
Last active January 20, 2026 18:57
Show Gist options
  • Select an option

  • Save andynu/f208909ccdf8d1e24627dc55c2def2a5 to your computer and use it in GitHub Desktop.

Select an option

Save andynu/f208909ccdf8d1e24627dc55c2def2a5 to your computer and use it in GitHub Desktop.
C4 Diagram skill, using d2 lang
name description
C4 Diagrams
Creating C4 architecture diagrams using D2 diagram language. Use when creating system context diagrams, container diagrams, component diagrams, code diagrams, deployment diagrams, or any software architecture visualization following the C4 model. Also applies when discussing architecture at different zoom levels or explaining system boundaries.

C4 Architecture Diagrams with D2

Create C4 model diagrams using D2's text-to-diagram syntax. Supports both graphical (SVG/PNG) and ASCII output.


C4 Model Overview

The C4 model provides four levels of zoom for software architecture:

Level Audience Shows Excludes
Context Everyone System + users + external systems Technologies, internal details
Container Technical Apps, databases, services Component internals
Component Developers Internal modules, responsibilities Code-level details
Code Developers Classes, interfaces, functions Usually auto-generated

Key principle: "Different levels of zoom for different audiences."


C4 Element Types

Core Abstractions

Person          → Users, actors, roles
Software System → The system being designed OR external systems
Container       → Applications, databases, file stores (deployable units)
Component       → Functional groupings within a container

Supporting Elements

Deployment Node → Physical/virtual infrastructure where containers run
Relationship    → Directed connections with descriptions

D2 Conventions for C4

Element Shapes

# Person - use person shape
user: Customer {
  shape: person
}

# Software System - rectangle with double border
system: Banking System {
  shape: rectangle
  style.double-border: true
}

# External System - dashed border
external: Email Provider {
  style.stroke-dash: 3
}

# Container - standard rectangle
api: API Application {
  shape: rectangle
}

# Database - cylinder shape
db: Database {
  shape: cylinder
}

# Component - rectangle (inside container)
auth: Auth Module {
  shape: rectangle
}

Color Conventions

Key rule: Use white text on dark backgrounds, black text on light backgrounds.

# Dark fills with white text (high contrast)
person: User {
  style.fill: "#08427b"      # Dark navy
  style.font-color: "#ffffff"
}

system: Main System {
  style.fill: "#1168bd"      # Medium blue
  style.font-color: "#ffffff"
}

external: External {
  style.fill: "#6c757d"      # Dark gray (NOT light gray #999)
  style.font-color: "#ffffff"
  style.stroke-dash: 3
}

container: Container {
  style.fill: "#1168bd"      # Medium blue
  style.font-color: "#ffffff"
}

# Light fills with black text (for code diagrams)
class_box: ClassName {
  style.fill: "#cce5ff"      # Light blue
  style.font-color: "#000000"
}

struct_box: StructName {
  style.fill: "#d4edda"      # Light green
  style.font-color: "#000000"
}

gem_box: ExternalGem {
  style.fill: "#ffeeba"      # Light yellow
  style.font-color: "#000000"
}

Contrast guidelines:

  • Dark backgrounds (#08427b, #1168bd, #6c757d) → white text (#ffffff)
  • Light backgrounds (#cce5ff, #d4edda, #ffeeba, #f0f7ff) → black text (#000000)
  • Avoid mid-tone grays (#999999) as they lack contrast with white text
  • Container boundaries: use light fill (#f0f7ff) with dark stroke (#1168bd)

D2 Syntax Notes

Multi-line labels - Use quoted strings with \n:

# Correct - quoted string with newlines
node: MyNode {
  label: "Title\n[Technology]\nDescription"
}

# Also correct - markdown block (top-level only)
node: |md
  **Title**
  [Technology]
  Description
|

Edge labels with brackets - Quote labels containing [] or /:

# Correct
a -> b: "Makes calls [JSON/HTTPS]"
a -> b: "HTTP GET [Faraday]"

# Incorrect - will fail
a -> b: Makes calls [JSON/HTTPS]

Double-border requires shape - Must specify shape: rectangle:

# Correct
system: Name {
  shape: rectangle
  style.double-border: true
}

Nested containers - Use simpler labels inside nested structures:

outer: Container {
  inner: Component {
    style.fill: "#85bbf0"
    label: "Name\n[Tech]\nDescription"  # Quoted, not markdown
  }
}

Level 1: System Context Diagram

Purpose: Big picture showing your system in context Audience: Everyone (technical and non-technical)

Template

# System Context Diagram: [System Name]
# Shows the system and its relationships with users and external systems

direction: right

# People
customer: Customer {
  shape: person
  style.fill: "#08427b"
  style.font-color: "#ffffff"
}

admin: Administrator {
  shape: person
  style.fill: "#08427b"
  style.font-color: "#ffffff"
}

# Your System (focus)
system: Internet Banking System {
  shape: rectangle
  style.fill: "#1168bd"
  style.font-color: "#ffffff"
  style.double-border: true
}

# External Systems
email: E-mail System {
  style.fill: "#999999"
  style.font-color: "#ffffff"
  style.stroke-dash: 3
}

mainframe: Mainframe Banking System {
  style.fill: "#999999"
  style.font-color: "#ffffff"
  style.stroke-dash: 3
}

# Relationships (describe the interaction, not the mechanism)
customer -> system: "Views account balances, makes payments"
admin -> system: "Manages users and configuration"
system -> email: Sends notifications
system -> mainframe: "Gets account info, makes payments"

Context Diagram Rules

Include:

  • Your software system (center focus)
  • Users/personas who interact with it
  • External systems it depends on or integrates with

Exclude:

  • Technologies, protocols, APIs
  • Internal structure
  • Deployment details

Level 2: Container Diagram

Purpose: Zoom into the system boundary showing major building blocks Audience: Technical people

Template

# Container Diagram: [System Name]
# Shows the containers (applications, data stores) within the system

direction: down

# External actors (from context)
customer: Customer {
  shape: person
  style.fill: "#08427b"
  style.font-color: "#ffffff"
}

# System boundary
system: Internet Banking System {
  style.fill: transparent
  style.stroke: "#1168bd"
  style.stroke-width: 2

  # Web Application
  webapp: Web Application {
    shape: rectangle
    style.fill: "#438dd5"
    style.font-color: "#ffffff"
    label: |md
      **Web Application**
      [Java/Spring MVC]
      Delivers static content and
      the single page application
    |
  }

  # API Application
  api: API Application {
    shape: rectangle
    style.fill: "#438dd5"
    style.font-color: "#ffffff"
    label: |md
      **API Application**
      [Java/Spring Boot]
      Provides banking functionality
      via JSON/HTTPS API
    |
  }

  # Database
  db: Database {
    shape: cylinder
    style.fill: "#438dd5"
    style.font-color: "#ffffff"
    label: |md
      **Database**
      [PostgreSQL]
      Stores user data, accounts,
      transactions
    |
  }
}

# External systems
email: E-mail System {
  style.fill: "#999999"
  style.stroke-dash: 3
}

# Relationships with technology/protocol
customer -> system.webapp: Visits using HTTPS
system.webapp -> system.api: Makes API calls to\n[JSON/HTTPS]
system.api -> system.db: Reads/writes\n[JDBC]
system.api -> email: Sends emails using\n[SMTP]

Container Diagram Rules

Include:

  • All containers within the system boundary
  • Technology choices (in brackets: [Java/Spring])
  • Brief responsibility descriptions
  • External systems and people that connect

Exclude:

  • Internal component structure
  • Deployment topology (clusters, load balancers)
  • Code-level details

What counts as a Container:

  • Web applications, mobile apps, desktop apps
  • Server-side APIs, microservices
  • Databases, file systems, blob storage
  • Message queues, event streams
  • Serverless functions

Level 3: Component Diagram

Purpose: Zoom into a single container showing internal components Audience: Software architects and developers

Template

# Component Diagram: [Container Name]
# Shows the components within this container

direction: right

# The container being decomposed
api: API Application {
  style.fill: transparent
  style.stroke: "#438dd5"
  style.stroke-width: 2

  # Controllers
  signin: Sign In Controller {
    shape: rectangle
    style.fill: "#85bbf0"
    label: |md
      **Sign In Controller**
      [Spring MVC Controller]
      Allows users to sign in
    |
  }

  accounts: Accounts Controller {
    shape: rectangle
    style.fill: "#85bbf0"
    label: |md
      **Accounts Controller**
      [Spring MVC Controller]
      Provides account information
    |
  }

  # Components
  security: Security Component {
    shape: rectangle
    style.fill: "#85bbf0"
    label: |md
      **Security Component**
      [Spring Security]
      Authentication and authorization
    |
  }

  facade: Banking Facade {
    shape: rectangle
    style.fill: "#85bbf0"
    label: |md
      **Banking Facade**
      [Spring Bean]
      Orchestrates banking operations
    |
  }

  # Internal connections
  signin -> security: Uses
  accounts -> facade: Uses
  facade -> security: Uses
}

# External elements this container interacts with
db: Database {
  shape: cylinder
  style.fill: "#438dd5"
}

mainframe: Mainframe {
  style.fill: "#999999"
  style.stroke-dash: 3
}

api.facade -> db: Reads/writes\n[JDBC]
api.facade -> mainframe: Makes API calls\n[XML/HTTPS]

Component Diagram Rules

Use sparingly: Only create if they add value. Consider automating generation.

Include:

  • Components within the container boundary
  • Technology implementation (in brackets)
  • Key responsibilities
  • Connected containers and external systems

Level 4: Code Diagram (Optional)

Purpose: Lowest level showing code structure Recommendation: Usually auto-generate from IDE or skip entirely

# Code Diagram: [Component Name]
# Usually auto-generated - manual creation rarely worthwhile

direction: down

# Classes
controller: AccountsController {
  shape: class
  +getAccounts(): List<Account>
  +getAccount(id): Account
  -validateAccess(): boolean
}

service: AccountService {
  shape: class
  +findAll(): List<Account>
  +findById(id): Account
}

repo: AccountRepository {
  shape: class
  <<interface>>
  +findAll(): List<Account>
  +findById(id): Account
}

controller -> service: uses
service -> repo: uses

Supplementary Diagrams

System Landscape Diagram

For enterprises with multiple systems:

# System Landscape: [Organization Name]
# Shows all systems and how they relate

direction: right

# People
customer: Customer {
  shape: person
}

# Internal Systems
banking: Banking System {
  style.double-border: true
}

atm: ATM System {
  style.double-border: true
}

# External Systems
credit: Credit Bureau {
  style.stroke-dash: 3
}

customer -> banking
customer -> atm
banking -> credit
atm -> banking

Deployment Diagram

# Deployment Diagram: [Environment Name]
# Shows infrastructure and container deployment

direction: down

# Cloud region
aws: AWS us-east-1 {
  style.fill: "#f5f5f5"

  # Availability zone
  az1: Availability Zone 1 {
    style.fill: "#e8e8e8"

    ec2: EC2 Instance {
      style.fill: "#d4d4d4"

      docker: Docker {
        api: API Container {
          style.fill: "#438dd5"
        }
      }
    }
  }

  rds: RDS {
    db: PostgreSQL {
      shape: cylinder
      style.fill: "#438dd5"
    }
  }
}

aws.az1.docker.api -> aws.rds.db: Reads/writes\n[JDBC/SSL]

Dynamic Diagram

For runtime behavior (numbered steps):

# Dynamic Diagram: [Use Case Name]
# Shows runtime interactions for a specific scenario

direction: right

user: Customer {
  shape: person
}
webapp: Web App
api: API
db: Database {
  shape: cylinder
}

user -> webapp: 1. Submits login form
webapp -> api: 2. POST /auth/login
api -> db: 3. SELECT user WHERE email=...
db -> api: 4. Returns user record
api -> webapp: 5. Returns JWT token
webapp -> user: 6. Redirects to dashboard

Relationship Labels

Good Labels (Specific)

# Describe WHAT and sometimes HOW
user -> webapp: Views account balances using
api -> db: Reads/writes account data\n[JDBC]
api -> email: Sends password reset emails\n[SMTP]

Bad Labels (Avoid)

# Too vague - don't use single words
user -> system: Uses  # Bad
a -> b: Calls        # Bad
x -> y: Connects     # Bad

Output Formats

Graphical (SVG/PNG)

# SVG (recommended for web/docs)
d2 diagram.d2 diagram.svg

# PNG (for images/presentations)
d2 diagram.d2 diagram.png

# With specific theme
d2 --theme 200 diagram.d2 diagram.svg

ASCII Output

For code comments and terminal documentation, use the d2ascii skill:

# ASCII output
d2 diagram.d2 diagram.txt

# Pure ASCII (maximum compatibility)
d2 diagram.d2 diagram.txt --ascii-mode=standard

ASCII limitations: No colors, no icons, simplified shapes. Keep diagrams simple for ASCII.


Review Checklist

Every Diagram Must Have

  • Title stating diagram type and scope
  • Key/legend explaining notation (especially colors)
  • Element type labels (Person, Software System, Container, Component)
  • Technology labels on containers/components (in brackets)
  • Meaningful relationship descriptions (not just "uses")
  • Protocol/mechanism on technical relationships

Common Mistakes

Mistake Fix
Missing diagram title Add descriptive title at top
Vague labels ("Uses", "Calls") Describe the interaction specifically
Missing technology labels Add [Technology] in brackets
Too much detail for the level Remove details, zoom in with next level
No key/legend Add legend explaining colors/shapes
Acronyms without explanation Define in legend

Workflow

1. Start with Context

Always begin with a System Context diagram:

  • Identify your system's boundaries
  • List all users/personas
  • Identify external system dependencies

2. Zoom Into Containers

For each system, create Container diagram:

  • List all deployable units
  • Show technology choices
  • Map communication paths

3. Component Detail (If Needed)

Only for complex containers where it adds value:

  • Show internal architecture
  • Document module responsibilities

4. Review and Iterate

  • Run through checklist above
  • Get feedback from team
  • Update as architecture evolves

Quick Reference

Shapes:

  • shape: person - Users/actors
  • shape: rectangle - Systems, containers, components
  • shape: cylinder - Databases
  • shape: class - Code-level (UML)

Styling:

  • style.double-border: true - Your system (focus)
  • style.stroke-dash: 3 - External systems
  • style.fill: "#color" - Background color

Commands:

  • d2 input.d2 output.svg - Graphical output
  • d2 input.d2 output.txt - ASCII output
  • d2 --theme 200 input.d2 output.svg - With theme

Critical Rules

Never

  • Create diagrams without titles - Always state type and scope
  • Use vague relationship labels - "Uses" tells nothing
  • Mix abstraction levels - Each diagram stays at one level
  • Show deployment in container diagrams - Use deployment diagrams
  • Skip the legend - Colors and shapes need explanation

Always

  • Label element types - Person, System, Container, Component
  • Include technology choices - [Java/Spring], [PostgreSQL]
  • Describe relationships specifically - What data flows, what action
  • Show direction - Arrows indicate dependency/data flow direction
  • Keep it simple - If diagram is cluttered, zoom in or split
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment