Live at arc42.org
Built with Jekyll and served by Netlify.
Stack: Jekyll 4 (Ruby) · Minimal Mistakes theme (fully vendored) · Docker Compose for local development · Netlify for build & deploy.
All development runs in Docker — no local Ruby, Bundler, or Jekyll needed, just Docker and make.
make build # build the dev image once (installs the pinned gems — needs network)
make dev # start Jekyll with live reload at http://localhost:4200make dev serves on 4200, the first slot in the arc42 42xx block. Every
site in the family has its own fixed port so their dev servers can run side by
side — see raw/port-assignment.md in meta.arc42.org for the full assignment.
The number is stated explicitly in three places rather than inherited from
Jekyll's default, so it stays honest if this site ever moves: SITE_PORT in the
Makefile, the mapping plus --port in docker-compose.yml, and EXPOSE/CMD
in the Dockerfile.
Everything after the first make build works fully offline (e.g. on a train): the
gems are baked into the Docker image and the theme is vendored into the repo, so no
build step reaches out to the network.
| Command | What it does |
|---|---|
make help |
List all targets. |
make dev |
Start the dev server with live reload at http://localhost:4200. |
make build |
Build the arc42-site:latest Docker image from the pinned gems. |
make site |
Generate the static site into _site/. |
make check-links |
Build, then validate internal links/images/HTML with html-proofer. |
make shell |
Open a shell inside the container for debugging. |
make install |
Refresh gems in the image after editing the Gemfile. |
make update |
Update gems to their latest allowed versions (rewrites Gemfile.lock). |
make stop |
Stop and remove the running container. |
make logs |
Tail the dev server logs. |
make clean |
Remove _site/ and local caches. |
After make update (or any change to Gemfile.lock), the container refuses to serve
until you re-run make build — the entrypoint compares the lockfile against the gems
baked into the image and fails loudly on drift, so you never run against stale gems.
The Minimal Mistakes theme (v4.24.0) is
vendored into this repository — its _layouts/, _includes/, _sass/, and
assets/js/ live here directly rather than being pulled from a remote theme or gem at
build time. This keeps builds fully offline and puts every template under version
control. arc42-specific customizations (masthead, footer, head, SCSS) sit alongside the
stock theme files and override them via Jekyll's normal precedence.
Netlify builds and deploys on push. The build configuration lives in
netlify.toml (build command, publish directory, JEKYLL_ENV=production)
and the Ruby version is pinned in .ruby-version — these override the
Netlify dashboard's build settings.
The /resources/ page (books, articles, talks,
videos) is data-driven. Every entry is a single Markdown file in the
_resources/ collection — no HTML to touch, no counts to maintain.
Drop one file in _resources/, e.g. _resources/my-new-talk.md:
---
type: talk # book | article | talk | video (required)
title: "My new talk" # required
language: en # en | de (required)
year: 2026 # optional — undated items sort to the end
summary: "One-line description shown on the card." # required
search: "extra author keywords for the filter" # optional
cover: /images/resources/my-new-talk.webp # optional (see Thumbnails)
link: https://speakerdeck.com/... # optional — omit for no link
link_label: "Open slides" # optional CTA text
id: my-anchor # optional — for deep links like /resources/#my-anchor
---That's it. The listing template (_includes/resource-item.html)
renders the card; _pages/resources.md loops the collection,
sorts by year descending (undated last), and computes the type-badge counts in
Liquid — so the numbers can never drift out of sync with the files.
The link arrow is derived from the URL, so it stays consistent:
internal →, external ↗ (plus rel="noopener"), and .pdf ↓.
Set cover: to any image path and the card shows that thumbnail. Omit it and the
card falls back to a color-coded letter marker (Book / Article / Talk /
Video). This works for every type, not just books.
- New thumbnails (articles, talks, videos): put them in
/images/resources/. - Existing book covers live in
/images/books/— leave them there.
The thumbnail box is portrait-ish (4rem × 5.5rem) with object-fit: contain, so
both portrait covers and landscape stills fit without distortion.
Each resource also gets its own page at /resources/<slug>/ (the collection is
output: true). Most are minimal landing pages generated from the front matter; add
detail: true to promote one and the listing card links to it ("More details →")
instead of straight out.
Write Markdown below the front matter and it becomes the body of that detail page
— headings, lists, a longer description, a cover, a buy/watch button (from link:).
_resources/effektive-softwarearchitekturen.md
is the worked example; the layout is _layouts/resource.html.
Remember:
sitemap.xmlis maintained by hand. When you publish adetail: truepage, add its URL there — the other auto-generated item pages are orphaned landing pages and are intentionally left out of the sitemap.
The translator credits in the /about/ page's
community section are data-driven from _data/translators.yml.
Add or edit people there — no HTML to touch.
One block per person (not per language):
- name: "Mario Giustiniani" # required
language: "Italiano" # required — in its own script
bio: "One to three sentences." # optional
portrait: "/images/translators/mario-giustiniani.webp" # optional
links: # optional — list of labelled links
- label: "GitHub"
url: "https://github.com/mgiustiniani"The About page (_pages/about.md) renders this in two tiers:
- Full credit roll — every entry, grouped by
language(co-translators of one language auto-join with commas and "and"). A name links to its firstlinks:entry when present. - "Meet some of our translators" — entries that have a
bioor aportrait, shown as cards. The whole tier stays hidden until at least one entry qualifies, so it never looks half-finished.
- With
portrait:→ the card shows that image (square, round-cropped via CSS). Put portraits in/images/translators/as ~320px squarewebp. - Without
portrait:→ the card falls back to an initials monogram on a tinted disc (e.g. "Damien Lucas" → DL), so cards with and without a photo sit together cleanly.
Styles live under .translator-grid / .translator-card in
assets/css/arc42-org.css. No sitemap entry is needed —
this is a section of an existing page, not a new URL.
Three pieces, no dependencies:
search.jsonbuilds the index at compile time — one entry per page that has a real front-matter title, which is also what keeps the redirect stubs in_pages/and the asset templates out of it. Roughly 14 entries.assets/js/arc42-search.jsfetches that index and does the matching and ranking in the browser._pages/search.htmlis the page itself: a search box, a results list, and a<script>tag.
Two rules in the matcher are worth knowing before you change it:
Terms match only at the start of a word. A plain substring test makes c4 match
every page mentioning arc42 — "arc42" — which is what the previous implementation
did, returning 13 of 14 pages. Prefix typing still works, so archi finds
"architecture". The boundary is expressed as (?:^|[^a-z0-9]) rather than a (?<!…)
lookbehind, because Safari only gained lookbehind in 16.4.
Results are scored, not just filtered. Title hits count 10, URL hits 4, body hits 1
each up to 5 per term, with ties broken by title so ordering is stable between builds.
Without this the results arrive in Jekyll's page-emission order, which put the
20-years blog post above /canvas/ for the query "canvas".
This replaced Simple-Jekyll-Search, which could do neither: its matcher was a raw
field.indexOf(term) and its sort comparator returned a constant 0.
The training block (_includes/subtle-ads/subtle-ads.html, embedded on five
pages: home, learn, documentation, examples, download) is rendered at build
time from _data/trainings.json — an expiry-filtered copy of
https://trainings.arc42.org/api/trainings.json that
.github/workflows/refresh-trainings.yml refreshes and commits only when the
dates actually changed. The workflow runs on three triggers: a weekly cron
(Mondays 05:47 UTC), manual workflow dispatch, and a repository_dispatch
event (trainings-updated) that the trainings repo pushes right after the
feed republishes — so date changes propagate within minutes instead of waiting
up to a week. The push is an accelerator, never a dependency (ADR-0006 in
meta.arc42.org): if it never arrives, the cron still bounds staleness at one
week, and a failed fetch simply leaves the last committed snapshot in place.
Edit dates in the trainings repo's _data/trainings.yml, never here;
_includes/subtle-ads/subtle-ads.html owns the rendering,
assets/css/subtle-ad.css the styling. This replaced the former runtime htmx
fetch from the Vercel fragment backend (see the integration spec in the arc42
workspace's docs/superpowers/specs/2026-08-04-docs-faq-training-dates-design.md).
- Free images can be found at Unsplash
- I bought several icons from IconFinder, a great source for professional-grade icons. Recommended!
- I generated the various favicon files with RealFavIconGenerator.
The arc42 template is licensed under a CreativeCommons Sharealike International 4.0 License.
You are free to:
- Share — copy and redistribute the template in any medium or format
- Adapt — remix, transform, and build upon the material for any purpose, even commercially.
Copyright (c) 2016ff Michael Rose
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.