Skip to content

Self-Hosting the Converters

This page is the end-to-end guide for adopting the converters in-house: host the library on your own GitLab, build a converter image into your registry, then reuse that image in your architecture projects and wire their output into your architecture graph.

It ties together three existing pieces:

  • .gitlab-ci.custom-registry.yml — build & push the image to your registry
  • Docker & CI Usage — per-notation CI job snippets
  • The example-architecture-project and example-archi-graph templates

If you just want to use an already-published image, skip to Step 2.


The shape of an in-house setup

flowchart LR
    subgraph build["1. Build the image (once)"]
        conv["linked-archi-converters<br/>(your fork/mirror)"]
        reg["Your container registry<br/>:v1.2.0 / :latest"]
        conv -->|"custom-registry CI:<br/>docker build + push"| reg
    end

    subgraph produce["2. Architecture projects"]
        proj["models/ + .gitlab-ci.yml<br/>image: CONVERTER_IMAGE"]
        art["out/*.trig<br/>(CI artifact)"]
        proj --> art
    end

    subgraph aggregate["3. Architecture graph"]
        idx["sources-index.yaml"]
        graph["graph/**/*.trig → merged"]
        idx --> graph
    end

    reg -->|"image: pulled by"| proj
    art -->|"pulled via<br/>Artifacts API"| graph

Step 1 — Build the image into your registry

Host the linked-archi-converters repo on your GitLab (fork, mirror, or import).

The repo ships a ready-made CI config for exactly this: .gitlab-ci.custom-registry.yml. Copy it to .gitlab-ci.yml (or include: it):

include:
  - local: .gitlab-ci.custom-registry.yml

It builds all six converter JARs and the two pull tools, then docker builds the multi-stage Dockerfile and pushes two tags to your registry:

  • $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  • $CI_REGISTRY_IMAGE:latest

Prerequisites (see the header of that file for the full list):

  • GitLab Container Registry enabled on your instance
  • A runner with Docker-in-Docker (docker:24-dind)
  • Network access to pull the base images (gradle:9-jdk25, eclipse-temurin:25-jre-alpine, docker:24)
  • CI_REGISTRY* variables (auto-provided when the registry is enabled)

Push to your default branch (or tag a release) and the image is published at:

<your-registry>/<group>/linked-archi-converters:latest

Tell the image what it is

The shipped config passes four build args, and a hand-rolled pipeline should too:

docker build \
  --build-arg "IMAGE_REF=$IMAGE_TAG" \
  --build-arg "SOURCE_REVISION=$CI_COMMIT_SHA" \
  --build-arg "IMAGE_VERSION=${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}" \
  --build-arg "SOURCE_URL=$CI_PROJECT_URL" \
  -t "$IMAGE_TAG" -t "$IMAGE_LATEST" .

A container cannot discover its own tag, so this is the only way it can say which build it is. Every argument is optional and the build succeeds without them — but graphs converted with that image then name no image and no source revision in their provenance, so a published IRI cannot be traced back to the code that produced it. Nothing warns you, because a converter that cannot identify itself says nothing rather than guessing.

This works because the pipeline builds from a copy of the converter repository, so CI_COMMIT_SHA and CI_PROJECT_URL describe the converter source. If you instead package downloaded JARs, your own commit is not the converter's — read the revision from the published SOURCE_REVISION and SOURCE_URL files, as in Building from downloaded JARs.

SOURCE_URL must be your project. It has no default for that reason: composing a commit link from your revision and someone else's project URL would publish a link to a commit that does not exist there.

The digest is deliberately not baked in — it does not exist until after the push. Downstream projects pinning image: …@sha256:… get digest-exact provenance from CI_JOB_IMAGE, which outranks anything baked in. See ADR 0008.

:latest moves. For reproducible conversions, publish semver tags too — uncomment the docker-release job in .gitlab-ci.custom-registry.yml and tag releases (v1.2.0), which produces …/linked-archi-converters:v1.2.0. Downstream projects should reference a version tag, not :latest, so a converter change never silently alters their output.

What's in the image

The Dockerfile copies each fat JAR into /opt/converters/ and generates a wrapper script per JAR into /usr/local/bin/, so every CLI is on PATH:

archimate2linkedarchi, bpmn2linkedarchi, plantuml2linkedarchi, structurizr2linkedarchi, backstage2linkedarchi, leanix2linkedarchi, plus backstage-pull and leanix-pull.

That's why a CI job with image: <your image> can call bpmn2linkedarchi … directly — no install step needed.


Step 2 — Use the image in an architecture project

In each architecture (source) project, point CONVERTER_IMAGE at your published image and run the relevant converter. Start from the example-architecture-project template, whose .gitlab-ci.yml already does this:

variables:
  CONVERTER_IMAGE: $CI_REGISTRY/<group>/linked-archi-converters:v1.2.0
  BASE_IRI: "https://archi.example.com/graph/"

stages:
  - convert
  - validate

convert:
  stage: convert
  image: $CONVERTER_IMAGE
  script:
    - |
      if ls models/bpmn/*.bpmn 2>/dev/null; then
        bpmn2linkedarchi convert models/bpmn/*.bpmn \
          --base-iri $BASE_IRI --format TRIG --include-di \
          --diagrams-index models/bpmn/diagram-index.yaml \
          -o out/bpmn.trig
      fi
  artifacts:
    paths:
      - out/
    expire_in: never          # so the graph repo can pull from main
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

See Docker & CI Usage for ready-made jobs for every notation.

Private registry access

If your registry is private, the runner in the architecture project must be able to pull the image across projects. Either:

  • grant the project a group deploy token with read_registry scope and log in (docker login), or
  • make the converters project's registry readable by the group.

Two requirements the graph relies on

  • artifacts: paths: must include your output (out/) with a long/never expiry on main — otherwise the Artifacts API pull returns 404.
  • The job name and artifact path must match what you'll declare in the graph's sources index (below). Renaming either silently breaks the pull.

Step 3 — Stand up the architecture graph

If you don't already have one, create your aggregation repo from the example-archi-graph template. Six steps get it running:

  1. Fork/copy the template into your GitLab as (e.g.) archi-group/archi-graph.

  2. Set CI/CD variables (Settings → CI/CD → Variables):

    Variable Scope / value Purpose
    GITLAB_TOKEN token with read_api + write_repository pull artifacts, push update branch, open MRs
    TRIPLESTORE_URL your SPARQL endpoint optional, for the publish step

    read_api must cover every source project the token pulls from. The graph repo normally needs no CONVERTER_IMAGE — it pulls pre-converted RDF, it doesn't convert.

  3. Configure sources-index.yaml — one entry per producing project. The job and artifact must match what the source project's CI publishes (Step 2):

    sources:
      - id: order-processes
        repo: teams/order-service         # GitLab project path
        job: convert                      # the CI job that produced the artifact
        artifact: out/bpmn.trig           # path within that job's artifacts
        target: graph/bpmn/order.trig     # where it lands in this repo
        tier: 1
        ref: v1.2.0                        # main, or a tag to pin a version
    

    To also aggregate Turtle, add a second entry per source targeting the .ttl artifact (artifact: out/bpmn.ttl, target: graph/bpmn/order.ttl).

  4. Set config/namespaces.yaml — your organisation's namespace prefixes.

  5. Write shapes/cross-model-rules.ttl — your Tier 2 governance rules (e.g. "every ApplicationComponent must have an owner"). The template ships example shapes to adapt.

  6. Create the scheduled pipeline (Build → Pipeline schedules) so pull-and-propose runs on your cadence. It pulls sources and, if anything changed, opens an MR for review. Optionally enable the commented publish-* jobs in .gitlab-ci.yml.

pull-sources.sh reads sources-index.yaml and downloads each artifact from the latest successful pipeline on ref via the GitLab Jobs Artifacts API. See Aggregating into a Graph for the full pull → validate → merge flow.


Checklist

  • [ ] linked-archi-converters hosted on your GitLab
  • [ ] .gitlab-ci.custom-registry.yml active → image published to your registry
  • [ ] Version tag published (e.g. :v1.2.0), not just :latest
  • [ ] Architecture project sets CONVERTER_IMAGE to that tag
  • [ ] Runner can pull the image (deploy token / readable registry)
  • [ ] convert job publishes out/ as an artifact with expire_in: never on main
  • [ ] archi-graph repo created from the template
  • [ ] GITLAB_TOKEN (read_api + write_repository) set in the graph repo
  • [ ] config/namespaces.yaml and shapes/cross-model-rules.ttl customised
  • [ ] Project registered in archi-graph/sources-index.yaml with matching job + artifact
  • [ ] Scheduled pipeline created to run pull-and-propose