agentleFS
jrjsmrtnjrjsmrtn/project-orchestration-skills/skills/setup-architecture-as-code/SKILL.md

setup-architecture-as-code

Skill17 starsChanged 10 days agoMITView on GitHub

Establish architecture-as-code practice using C4 modeling and Structurizr DSL. Use after bootstrap-project, when establishing architecture documentation, or when migrating from diagram-based to code-based architecture.

The file

We split this skill into the rules it gives an agent

8 rules. About 0 are common, 6 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    After running bootstrap-project skill

    line 16
  2. Distinctive:

    When establishing architecture documentation for a project

    line 17
  3. Distinctive:

    When migrating from diagram-based to code-based architecture documentation

    line 18
  4. Distinctive:

    When you need version-controlled, validated architecture models

    line 19
  5. Distinctive:

    Key Principle: Start with Level 1-2, add Level 3 only when needed.

    line 61
  6. Very specific:

    Add to project Makefile:

    line 442
  7. Distinctive:

    Add components later: Only add Component level when needed

    line 533
  8. Distinctive:

    When a single workspace.dsl grows too large, split into focused workspaces per subsystem:

    line 592

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Setup Architecture-as-Code

Establish architecture-as-code practice using C4 modeling and Structurizr DSL.

When to Use

  • After running bootstrap-project skill
  • When establishing architecture documentation for a project
  • When migrating from diagram-based to code-based architecture documentation
  • When you need version-controlled, validated architecture models

What is Architecture-as-Code?

Architecture-as-Code treats architecture documentation like source code:

Traditional DiagramsArchitecture-as-Code
Draw in GUI toolsDefine in text/DSL
Manual updatesAutomated validation
Binary files (PNG, VSDX)Text files (version control friendly)
Diagrams diverge from realitySingle source of truth
Hard to review changesPR-reviewable changes
No validationSyntax + semantic validation

Implementation Stack:

  • Modeling Approach: C4 Model (Context, Container, Component, Code)
  • Definition Language: Structurizr DSL
  • Validation & Inspection: structurizr/structurizr (consolidated image, replaces deprecated structurizr/cli and structurizr/lite), pinned to a dated tag (currently 2026.09.19; latest moves)
  • Runtime: the most native OCI runtime available: Apple container on Apple Silicon macOS, wslc on Windows where available, else podman (Docker works too)
  • Visualization: structurizr/structurizr local subcommand or Structurizr Cloud

Required Inputs

  1. Project/System name
  2. System description (1-2 sentences)
  3. Key users/personas (who interacts with the system)
  4. External systems (what the system integrates with)
  5. Main containers (applications, databases, services)
  6. Technology stack (languages, frameworks, databases)

C4 Model Overview

The C4 model provides four levels of abstraction (zoom levels):

LevelNamePurposeAudience
1System ContextSystem in its environmentEveryone
2ContainerDeployable unitsTechnical staff
3ComponentInternal structureDevelopers
4CodeClass/module levelDevelopers (often generated)

Key Principle: Start with Level 1-2, add Level 3 only when needed. Level 4 is usually generated from code.

Workflow

Step 1: Create Directory Structure

mkdir -p architecture/shared
mkdir -p docs/architecture    # Structurizr-specific docs (h2-first, numbered files)
# Symlink so !adrs and !docs directives can reach docs/ from architecture/
ln -s ../docs architecture/docs

The symlink is required because Structurizr restricts !adrs and !docs paths to the same directory or subdirectories of the DSL file — it cannot traverse parent directories.

Why docs/architecture/ and not docs/explanation/? Structurizr silently strips h1 headings (it auto-generates h1 from the element name). Diátaxis docs use h1 as their title, so pointing !docs at a Diátaxis directory would lose those titles. Keep Structurizr docs in a dedicated directory with h2-first files.

Step 2: Create Workspace File

Create architecture/workspace.dsl:

workspace "[Project Name]" "[Brief description]" {

    !identifiers hierarchical
    !adrs docs/adr
    !docs docs/architecture

    model {
        # ===== USERS/ACTORS =====
        user = person "User" "Primary user of the system" "User"

        # Add other personas as needed:
        # admin = person "Administrator" "System administrator" "Admin"
        # developer = person "Developer" "API consumer" "Developer"

        # ===== EXTERNAL SYSTEMS =====
        # externalSystem = softwareSystem "External System" "Description" "External"

        # ===== THE SYSTEM =====
        system = softwareSystem "[System Name]" "[System description]" {

            # ----- CONTAINERS -----

            # Web Application (if applicable)
            webApp = container "Web Application" "Provides user interface" "[Framework]" "Web"

            # API Service (if applicable)
            api = container "API Service" "Provides REST/GraphQL API" "[Framework]" "API"

            # Database
            database = container "Database" "Stores application data" "[Database Technology]" "Database"

            # Background Workers (if applicable)
            # worker = container "Background Worker" "Processes async tasks" "[Technology]" "Worker"

            # ----- COMPONENTS (for main container) -----
            # Uncomment and customize for your architecture:

            # api {
            #     authComponent = component "Authentication" "Handles user auth" "[Module]"
            #     businessComponent = component "Business Logic" "Core domain logic" "[Module]"
            #     dataComponent = component "Data Access" "Database operations" "[Module]"
            # }
        }

        # ===== RELATIONSHIPS =====

        # User interactions
        user -> system "Uses"
        user -> system.webApp "Interacts with" "HTTPS"

        # Internal relationships
        system.webApp -> system.api "Makes API calls to" "HTTP/JSON"
        system.api -> system.database "Reads from and writes to" "SQL"

        # External system relationships (if any)
        # system.api -> externalSystem "Integrates with" "HTTPS"
    }

    views {
        # ===== SYSTEM CONTEXT VIEW =====
        systemContext system "SystemContext" {
            include *
            autoLayout
            description "System Context diagram showing [System Name] and its environment"
        }

        # ===== CONTAINER VIEW =====
        container system "Containers" {
            include *
            autoLayout
            description "Container diagram showing the main deployable units"
        }

        # ===== COMPONENT VIEW (optional) =====
        # Uncomment when you have components defined:
        # component system.api "Components" {
        #     include *
        #     autoLayout
        #     description "Component diagram for the API service"
        # }

        # ===== STYLES =====
        styles {
            element "Person" {
                shape Person
                background #08427B
                color #ffffff
            }
            element "Software System" {
                background #1168BD
                color #ffffff
            }
            element "External" {
                background #999999
                color #ffffff
            }
            element "Container" {
                background #438DD5
                color #ffffff
            }
            element "Component" {
                background #85BBF0
                color #000000
            }
            element "Database" {
                shape Cylinder
            }
            element "Web" {
                shape WebBrowser
            }
            element "API" {
                shape Hexagon
            }
            element "Worker" {
                shape Robot
            }
        }
    }
}

Step 3: Create Technology-Specific Templates

Elixir/Phoenix Template

workspace "[Project Name]" "Elixir/Phoenix application" {

    !identifiers hierarchical
    !adrs docs/adr
    !docs docs/architecture

    model {
        user = person "User" "Application user" "User"

        system = softwareSystem "[System Name]" "Phoenix web application" {

            phoenix = container "Phoenix Application" "Web application and API" "Elixir/Phoenix" "Web"

            liveview = container "LiveView UI" "Real-time user interface" "Phoenix LiveView" "Web"

            # If using Ash Framework:
            # ash = container "Ash Resources" "Domain logic and resources" "Ash Framework" "Component"

            postgres = container "PostgreSQL" "Primary data store" "PostgreSQL 16" "Database"

            # Optional: Background jobs
            # oban = container "Oban Workers" "Background job processing" "Oban" "Worker"
        }

        user -> system.liveview "Uses" "HTTPS"
        system.liveview -> system.phoenix "LiveView connections" "WebSocket"
        system.phoenix -> system.postgres "Queries" "Ecto"
    }

    views {
        systemContext system "SystemContext" {
            include *
            autoLayout
        }

        container system "Containers" {
            include *
            autoLayout
        }

        styles {
            element "Person" {
                shape Person
                background #08427B
                color #ffffff
            }
            element "Software System" {
                background #6B4C9A
                color #ffffff
            }
            element "Container" {
                background #9B59B6
                color #ffffff
            }
            element "Database" {
                shape Cylinder
                background #336791
            }
            element "Web" {
                shape WebBrowser
            }
        }
    }
}

Python/FastAPI Template

workspace "[Project Name]" "Python FastAPI application" {

    !identifiers hierarchical
    !adrs docs/adr
    !docs docs/architecture

    model {
        user = person "User" "API consumer" "User"

        system = softwareSystem "[System Name]" "FastAPI service" {

            api = container "FastAPI Service" "REST API" "Python/FastAPI" "API"

            postgres = container "PostgreSQL" "Primary data store" "PostgreSQL 16" "Database"

            # Optional: Redis for caching/sessions
            # redis = container "Redis" "Cache and session store" "Redis" "Database"

            # Optional: Celery workers
            # celery = container "Celery Workers" "Async task processing" "Celery" "Worker"
        }

        user -> system.api "Calls" "HTTPS/JSON"
        system.api -> system.postgres "Queries" "SQLAlchemy"
    }

    views {
        systemContext system "SystemContext" {
            include *
            autoLayout
        }

        container system "Containers" {
            include *
            autoLayout
        }

        styles {
            element "Person" {
                shape Person
                background #08427B
                color #ffffff
            }
            element "Software System" {
                background #306998
                color #ffffff
            }
            element "Container" {
                background #FFD43B
                color #000000
            }
            element "Database" {
                shape Cylinder
                background #336791
            }
            element "API" {
                shape Hexagon
            }
        }
    }
}

Step 4: Create Model Overview

Create architecture/README.md:

# [Project Name] Architecture-as-Code

## What is Architecture-as-Code?

This project uses **Architecture-as-Code**: architecture models defined in text files (Structurizr DSL), version-controlled alongside source code, and validated automatically.

**Benefits**:
- **Version Control**: Architecture changes tracked in git history
- **Code Review**: Architecture changes reviewable in PRs
- **Validation**: Syntax and semantic errors caught automatically
- **Single Source of Truth**: Model generates diagrams, not vice versa
- **AI-Friendly**: Text-based format readable by AI assistants

## Model Structure

architecture/ ├── workspace.dsl # C4 model definition (THE source of truth) ├── shared/ # Shared DSL fragments (!include targets) │ └── _styles.dsl # Unified element/relationship styles ├── docs -> ../docs # Symlink (enables !adrs and !docs directives) ├── README.md # This file └── diagrams/ # Generated exports (optional, gitignored)


The `docs` symlink allows workspace files to use `!adrs docs/adr` and `!docs docs/architecture` — Structurizr requires these paths to be within or below the DSL file's directory.

## Quick Start

### View the Architecture

```bash
# Start the local viewer (replaces Structurizr Lite)
make view-architecture
# or (mount the project root, not just architecture/, so the docs symlink resolves):
$RUNTIME run --rm -p 8080:8080 \
  -v "$(pwd):/usr/local/structurizr" \
  -e STRUCTURIZR_WORKSPACE_PATH=architecture \
  structurizr/structurizr:2026.09.19 local

Then open http://localhost:8080

Validate the Model

make validate-architecture
# or (mount the project root so !adrs/!docs symlinks resolve):
$RUNTIME run --rm \
  -v "$(pwd):/work" \
  structurizr/structurizr:2026.09.19 validate -w /work/architecture/workspace.dsl

Export Diagrams

make export-architecture
# or (mount the project root so !adrs/!docs symlinks resolve):
$RUNTIME run --rm \
  -v "$(pwd):/work" \
  structurizr/structurizr:2026.09.19 export -w /work/architecture/workspace.dsl -format plantuml

C4 Model Levels

LevelViewWhat It Shows
1System Context[System] in its environment with users and external systems
2ContainerDeployable units: [list your containers]
3ComponentInternal structure of [main container] (if defined)

Updating the Architecture

  1. Edit workspace.dsl with your changes
  2. Validate: make validate-architecture
  3. Review: make view-architecture to see visual changes
  4. Document: Create/update ADR if this is a significant decision
  5. Commit: Include DSL changes in your PR

Architecture Decision Records

See ../adr/ for ADRs explaining the rationale behind architectural decisions.

Key ADRs:

  • ADR-0002: Establishes architecture-as-code practice
  • ADR-0003: Technology stack decision

References


### Step 5: Add Makefile Targets

Add to project `Makefile`:

```makefile
# Architecture-as-Code targets
.PHONY: validate-architecture inspect-architecture view-architecture export-architecture

# Most native OCI runtime first; override with CONTAINER_RUNTIME=podman etc.
# Apple `container` needs `container system start` once per boot.
CONTAINER_RUNTIME ?= $(shell for r in container wslc podman docker; do command -v $$r >/dev/null 2>&1 && { echo $$r; break; }; done)
# Pin a dated tag: `latest` moves, and a newer release can reject a model an older one accepted.
STRUCTURIZR_IMAGE ?= structurizr/structurizr:2026.09.19

validate-architecture:
	@echo "Validating architecture model..."
	$(CONTAINER_RUNTIME) run --rm \
		-v "$(PWD):/work" \
		$(STRUCTURIZR_IMAGE) validate -w /work/architecture/workspace.dsl

inspect-architecture:
	@echo "Inspecting architecture model (Checkstyle-style rules)..."
	$(CONTAINER_RUNTIME) run --rm \
		-v "$(PWD):/work" \
		$(STRUCTURIZR_IMAGE) inspect -w /work/architecture/workspace.dsl -s error,warning

view-architecture:
	@echo "Starting Structurizr local viewer on http://localhost:8080..."
	@echo "Press Ctrl+C to stop"
	$(CONTAINER_RUNTIME) run --rm -p 8080:8080 \
		-v "$(PWD):/usr/local/structurizr" \
		-e STRUCTURIZR_WORKSPACE_PATH=architecture \
		$(STRUCTURIZR_IMAGE) local

export-architecture:
	@echo "Exporting architecture to PlantUML..."
	$(CONTAINER_RUNTIME) run --rm \
		-v "$(PWD):/work" \
		$(STRUCTURIZR_IMAGE) export -w /work/architecture/workspace.dsl -format plantuml -output /work/architecture/diagrams/

export-architecture-json:
	@echo "Exporting architecture to JSON..."
	$(CONTAINER_RUNTIME) run --rm \
		-v "$(PWD):/work" \
		$(STRUCTURIZR_IMAGE) export -w /work/architecture/workspace.dsl -format json

Step 6: Validate and Commit

# Validate the model
make validate-architecture
# or manually (mount the project root so !adrs/!docs symlinks resolve):
$RUNTIME run --rm -v "$(pwd):/work" \
  structurizr/structurizr:2026.09.19 validate -w /work/architecture/workspace.dsl

# View to verify (optional)
make view-architecture

# Commit
git add architecture/
git add Makefile  # if updated
git commit -m "docs: add architecture-as-code model (C4/Structurizr)"

Outputs

This skill creates:

  • architecture/workspace.dsl (C4 model with !adrs and !docs integration)
  • architecture/docs symlink to ../docs (enables Structurizr documentation directives)
  • architecture/shared/ directory (for shared DSL fragments like _styles.dsl)
  • architecture/README.md (architecture-as-code documentation)
  • Makefile targets (validate-architecture, view-architecture, export-architecture)

Validation

# Verify workspace.dsl exists and is valid
make validate-architecture

# Expected output:
# Validating architecture model...
# workspace.dsl: OK

# View the model
make view-architecture
# Open http://localhost:8080 and verify diagrams render correctly

Architecture-as-Code Best Practices

  1. Model is source of truth: Diagrams are outputs, DSL is the source
  2. Start simple: Begin with System Context and Container views
  3. Add components later: Only add Component level when needed
  4. Validate in CI: Include make validate-architecture in CI pipeline
  5. Review in PRs: Architecture changes should be code-reviewed
  6. Keep descriptions short: 1-2 sentences per element
  7. Update with code: Architecture should evolve with implementation
  8. Reference in ADRs: Link architectural decisions to model elements
  9. Don't export diagrams to git: Generate on demand, don't commit PNGs

Common Patterns

Microservices

service1 = container "Service 1" "Description" "Technology"
service2 = container "Service 2" "Description" "Technology"
messageQueue = container "Message Queue" "Async communication" "RabbitMQ" "Queue"

service1 -> messageQueue "Publishes events"
service2 -> messageQueue "Consumes events"

API Gateway

gateway = container "API Gateway" "Routes requests" "Kong/Nginx" "API"
gateway -> service1 "Routes to"
gateway -> service2 "Routes to"
user -> gateway "All requests"

Caching Layer

cache = container "Cache" "Caches responses" "Redis" "Database"
api -> cache "Checks cache"
api -> database "Cache miss"

AI Collaboration

Architecture-as-Code is particularly AI-friendly:

AspectBenefit for AI
Text-basedAI can read and understand DSL directly
StructuredClear syntax enables accurate modifications
ValidatedAI changes can be validated automatically
Version-controlledAI can see architecture history

AI can help with:

  • Generating initial workspace.dsl from project description
  • Adding new containers/components
  • Suggesting relationships between elements
  • Reviewing architecture changes for consistency

Human should lead on:

  • Major architectural decisions
  • Strategic technology choices
  • Validation of business alignment

Growing to Multiple Workspaces

When a single workspace.dsl grows too large, split into focused workspaces per subsystem:

architecture/
├── workspace.dsl                    # Master — all subsystems at container level
├── <name>-<subsystem>-workspace.dsl # Focused — one subsystem at component level
├── shared/
│   └── _styles.dsl                  # Unified styles shared via !include
├── docs -> ../docs
└── README.md

Each focused workspace:

  • Promotes its subsystem to a top-level softwareSystem with full container/component detail
  • Declares other subsystems as one-line "Sibling" stubs with context-specific descriptions
  • Repeats !adrs docs/adr and !docs docs/architecture (these are per-workspace, not inherited)
  • Uses styles { !include shared/_styles.dsl } for style consistency

Why not share sibling stubs or people via !include? Structurizr errors on duplicate identifiers (no merge, no override). Each focused workspace defines its primary system with the same identifier as the stub would use. Also, descriptions are intentionally context-specific — a sibling description explains how that subsystem relates to this workspace's focus.

Podman note: mount the project root (not just architecture/) so the docs symlink resolves inside the container.

Related Skills

  • bootstrap-project - Creates initial directory structure
  • setup-adrs - ADR-0002 documents architecture-as-code practice
  • analyze-project - SPARK analysis informs architecture
  • plan-sprint - Architecture updates tracked in sprints

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.