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-projectandexample-archi-graphtemplates
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):
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:
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.
Pin versioned tags (recommended)¶
: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_registryscope 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/neverexpiry onmain— 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:
-
Fork/copy the template into your GitLab as (e.g.)
archi-group/archi-graph. -
Set CI/CD variables (Settings → CI/CD → Variables):
Variable Scope / value Purpose GITLAB_TOKENtoken with read_api+write_repositorypull artifacts, push update branch, open MRs TRIPLESTORE_URLyour SPARQL endpoint optional, for the publish step read_apimust cover every source project the token pulls from. The graph repo normally needs noCONVERTER_IMAGE— it pulls pre-converted RDF, it doesn't convert. -
Configure
sources-index.yaml— one entry per producing project. Thejobandartifactmust 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 versionTo also aggregate Turtle, add a second entry per source targeting the
.ttlartifact (artifact: out/bpmn.ttl,target: graph/bpmn/order.ttl). -
Set
config/namespaces.yaml— your organisation's namespace prefixes. -
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. -
Create the scheduled pipeline (Build → Pipeline schedules) so
pull-and-proposeruns on your cadence. It pulls sources and, if anything changed, opens an MR for review. Optionally enable the commentedpublish-*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-convertershosted on your GitLab - [ ]
.gitlab-ci.custom-registry.ymlactive → image published to your registry - [ ] Version tag published (e.g.
:v1.2.0), not just:latest - [ ] Architecture project sets
CONVERTER_IMAGEto that tag - [ ] Runner can pull the image (deploy token / readable registry)
- [ ]
convertjob publishesout/as an artifact withexpire_in: neveronmain - [ ]
archi-graphrepo created from the template - [ ]
GITLAB_TOKEN(read_api+write_repository) set in the graph repo - [ ]
config/namespaces.yamlandshapes/cross-model-rules.ttlcustomised - [ ] Project registered in
archi-graph/sources-index.yamlwith matchingjob+artifact - [ ] Scheduled pipeline created to run
pull-and-propose