¯\_(ツ)_/¯
962 stories
·
3 followers

Fähren für Zugreisende: Skandinavien und Ostseeraum

1 Share
Fähren für Zugreisende: Skandinavien und Ostseeraum

Klar, wenn wir auf Zugreise gehen, wollen wir natürlich vor allem mit dem Zug fahren. Aber manchmal ist es auch eine gute Idee, eine Fähre in den Reiseplan einzubauen. Fähren schließen Lücken, Fähren können Zeit sparen. Und manchmal ist die Fährfahrt so schön, dass sie selbst zu einem Höhepunkt der Reise wird.

Aber welche Verbindungen nehmen überhaupt Fußpassagiere mit? Wie gut sind die Fährterminals an Bahn und Bus angebunden? Und welche Routen lohnen sich wirklich? Mit dieser Reihe möchte ich den wichtigsten Fährverbindungen Europas auf den Grund gehen – aus der Perspektive von Zugreisenden.

Im ersten Teil schauen wir nach Skandinavien und in den Ostseeraum: Fähren nach Schweden, Norwegen und Finnland sowie in die baltischen Staaten Litauen, Lettland und Estland.

💡
Dies ist Version 1 dieses Guides. Ich werde ihn nach und nach um weitere Informationen, Fotos und Karten ergänzen.
Fähren für Zugreisende: Skandinavien und Ostseeraum

Schweden

Seit der Eröffnung der Öresundbrücke im Jahr 2000 lässt sich Schweden bequem auf dem Landweg erreichen. Trotzdem bleibt die Fähre eine interessante Alternative – weil der Fahrplan besser passt, der Nachtzug zu teuer ist oder weil ihr die Öresundbrücke einmal aus einer anderen Perspektive erleben möchtet.

Travemünde – Trelleborg

Fahrzeit: 8 Stunden

Trelleborg ist das klassische Tor nach Schweden. Die Kleinstadt etwa 30 Kilometer südlich von Malmö hat eine Reihe von Fährverbindungen von Deutschland und Polen. Die Fährlinie von Travemünde bei Lübeck ist so bedeutend, dass mit TT-Line eine ganze Reederei nach den Anfangsbuchstaben der Städte benannt ist.

In Trelleborg liegt das Fährterminal direkt neben dem Bahnhof. Es gibt einen Shuttleservice, mit dem es von Bord direkt zum Bahnsteig geht. Auch die Anreise zum Terminal in Travemünde ist inzwischen einfacher geworden: Seit Ende 2025 verbindet ein Direktbus den Lübecker Hauptbahnhof mit dem Skandinavienkai.

Anbieter:

⚠️
Achtung: Der Bahnhaltepunkt „Lübeck-Travemünde Skandinavienkai“ hat keine Busverbindung zum Terminal mehr. Zu Fuß lässt sich das Hafengelände von hier nicht erreichen.

Travemünde – Malmö

Fahrzeit: 9 Stunden

Eine Alternative ist die Fahrt direkt nach Malmö. Höhepunkt ist die Passage unter der Öresundbrücke kurz vor der Ankunft. Die Fähre verkehrt ganzjährig bis zu dreimal täglich, darunter mindestens eine Nachtfahrt und je nach Tag auch eine Tagesüberfahrt.

Fußpassagiere werden mit einem Shuttle an Bord gebracht, Fahrräder werden kostenlos befördert. Das Terminal in Malmö liegt im Hafengebiet nördlich der Innenstadt. Von der Haltestelle „Norra Hamnen Verkö“ geht es in etwa 25 Minuten mit dem Stadtbus zum Hauptbahnhof Malmö C.

Tipp: Da die Linie von einer finnischen Reederei betrieben wird, gibt es an Bord natürlich eine Sauna. Die Nutzung ist für alle Fahrgäste kostenlos.

Anbieter:

Rostock – Trelleborg

Fahrzeit: 6 Stunden

Ebenfalls nach Trelleborg geht es von Rostock aus, und das mit gleich zwei verschiedenen Fähranbietern. Vorteil Rostock: Die Überfahrt geht schneller und die Anreise aus dem Osten Deutschlands ist kürzer. Von Berlin nach Rostock sind es etwa nur zwei Stunden mit dem Intercity.

Der Fährhafen von Rostock liegt etwas außerhalb des Stadtzentrums, ist aber gut mit dem ÖPNV zu erreichen. Vom Hauptbahnhof geht es mit der S-Bahn Richtung Warnemünde, dort steigt ihr in den Bus zur Haltestelle „Seehafen Fähre“ um.

Anbieter:

Kiel – Göteborg

Fahrzeit: 15,5 Stunden

Die Option für alle, die besonders bequem nach Schweden reisen möchten. An Bord steht klar das Erlebnis im Vordergrund. Es gibt Shopping, Restaurants und komfortable Kabinen. Aber klar: Das hat seinen Preis. Die Fähre Kiel–Göteborg ist die teuerste Route nach Schweden.

Aufgrund der Länge der Fahrt verkehren die Schiffe zwischen Kiel und Göteborg über Nacht, eine Kabine ist Pflicht. Genug Zeit, das Angebot an Bord zu genießen und morgens in Ruhe zu frühstücken.

In Kiel liegt das Terminal direkt im Stadtzentrum, nur etwa 800 Meter vom Hauptbahnhof entfernt. Es ist zu Fuß oder mit dem Bus erreichbar. In Göteborg legen die Fähren am Göta-Fluss nahe der Älvsborgsbron an. Von dort geht es mit der Straßenbahn (Haltestelle „Chapmans Torg“) in rund 15 Minuten ins Stadtzentrum.

Anbieter:

Helsingør – Helsingborg

Fahrzeit: 20 Minuten

Dieser kleine Hüpfer ist etwas für alle, die die Öresundbrücke schon kennen und mal etwas anderes ausprobieren möchten. Es ist ein kleiner Ausflug in die Geschichte, denn zwischen dem dänischen Helsingør und dem schwedischen Helsingborg verkehrten die Eisenbahnfähren, bevor die Brücke eröffnet wurde.

Und auch heute noch kann man die Fähre über den Öresund nehmen. Vorbildlich ist die Anbindung allemal: Sowohl in Helsingør als auch in Helsingborg liegt das Terminal direkt neben dem Bahnhof, über Gangways geht es direkt vom Zug aufs Schiff. Helsingør ist außerdem nur knapp eine Stunde mit dem Regionalzug von Kopenhagen entfernt.

Tipp: Wenn euch die Überfahrt zu kurz ist, könnt ihr einfach an Bord bleiben. Mit einem Ticket kann man beliebig oft hin und her fahren – und zum Beispiel ein Mittagessen an Bord genießen.

Anbieter:

Frederikshavn – Göteborg

Fahrzeit: 3,5 Stunden

Die zweite verbliebene Fährverbindung zwischen Dänemark und Schweden verkehrt weiter nördlich: von Frederikshavn an der Nordspitze Jütlands nach Göteborg, Schwedens zweitgrößter Stadt. Die entspannte, dreieinhalbstündige Überfahrt lässt sich wunderbar in eine kleine Rundreise durch Dänemark und Südschweden integrieren.

Frederikshavn wird stündlich mit Regionalzügen aus Aalborg bedient. Vom Bahnhof sind es nur wenige Minuten zu Fuß zum Fährterminal. In Göteborg legen die Fähren am Danmarksterminal an, von dort geht es mit der Straßenbahn weiter ins Stadtzentrum.

Anbieter:

⚠️
Achtung: Die Fährverbindung zwischen Grenå und Halmstad wurde im April 2026 eingestellt.

Gdynia – Karlskrona

Fahrzeit: 10 Stunden

Einen regen Fährverkehr nach Schweden gibt es auch von Polen. Herausstellen möchte ich hier die Fähre von Gdynia nach Karlskrona. Der Grund: Mit bis zu drei Abfahrten pro Tag gibt es einen recht dichten Takt, dazu sind die Preise oft vergleichsweise moderat.

Außerdem ist die Anreise unkompliziert. Nach Gdynia geht es direkt mit dem Zug aus Berlin. Und wer Lust auf einen schönen Bahntag in tschechischen und polnischen Eurocity-Zügen hat, könnte sogar die Anreise aus Wien über Gdynia in Erwägung ziehen.

Gdynia liegt etwa 40 Kilometer nördlich von Gdańsk (Danzig). Mit dem Bus sind es nur wenige Minuten vom Bahnhof zum Fährterminal. Karlskrona ist eine sehenswerte Marinestadt im Südosten Schwedens. Das Fährterminal liegt etwas außerhalb des Zentrums, mit dem Stadtbus geht es in etwa 30 Minuten zum Bahnhof.

Anbieter:

Fähren ab Świnoujście

Der wichtigste polnische Fährhub in Richtung Schweden ist Świnoujście (Swinemünde). Die Stadt liegt am östlichen Ende der Insel Usedom und ist über die Usedomer Bäderbahn direkt mit Stralsund verbunden. Aus Richtung Mecklenburg-Vorpommern oder Berlin kann eine Fähre von Świnoujście daher eine sinnvolle Option für die Anreise nach Schweden sein.


Norwegen

Eine Zugfahrt nach Norwegen ist spannend – aber auch lang. Da kann es eine gute Idee sein, einen Teil der Reise aufs Meer zu verlegen. Mit der Verbindung von Kiel nach Oslo gibt es eine direkte Route von Deutschland. Dazu kommen mehrere Fähren ab Dänemark, die je nach Reiseziel und Reiseplan ebenfalls interessant sein können.

Kiel – Oslo

Fahrzeit: 20 Stunden

Die Fahrt mit der einzigen Direktfähre von Deutschland nach Norwegen dauert fast einen ganzen Tag und, sagen wir es frei heraus, ist extrem teuer. Dafür warten auf den „Kreuzfahrtschiffen mit Autodeck“, wie sie die Reederei nennt, ein Dutzend Restaurants und Bars, eine Einkaufspromenade, ein Schwimmbad und vieles mehr.

Der Norwegen-Terminal in Kiel liegt auf der anderen Seite der Förde, ist aber nur einen rund zehnminütigen Spaziergang über die Hörnbrücke vom Hauptbahnhof entfernt.

Tipp: Das Ganze lässt sich auch als dreitägige Mini-Kreuzfahrt machen. Ob zwei Tage auf See für einen Landgang von gerade einmal vier Stunden in Oslo wirklich lohnen, muss jeder selbst entscheiden. Immerhin sind die Preise oft günstiger als bei einer einfachen Überfahrt.

Anbieter:

Kopenhagen – Oslo

Fahrzeit: 17 Stunden

Eine pfiffige Alternative kann es sein, stattdessen von Kopenhagen nach Oslo zu fahren. Die Route ist deutlich günstiger als die Fähre von Kiel. Natürlich ist die Anfahrt nach Kopenhagen länger, aber von Hamburg aus kommt ihr in einigen Stunden bequem im Eurocity hin. Seit 2026 gibt es auch wieder einen Direktzug von Prag über Dresden und Berlin.

In Kopenhagen gibt es einen Shuttlebus vom Hauptbahnhof sowie vom Bahnhof Nørreport zum Terminal. Die Tickets müssen extra gebucht werden.

Der Terminal in Oslo liegt zentral am Fuße der Festung Akershus. Zum Hauptbahnhof ist es nur ein kurzer Hüpfer mit dem Bus – oder ein schöner Spaziergang direkt am Wasser. Zum Opernhaus und ins Stadtzentrum sind es zu Fuß nur etwa 15 Minuten.

Anbieter:

Fähren ab Hirtshals

Weitere Fähren von Dänemark nach Norwegen verkehren ab Hirtshals an der Spitze Jütlands. Der Ort ist gut mit dem Zug zu erreichen und der Bahnhof liegt direkt neben den Fährterminals. Die Anreise über Aarhus und Aalborg ist allerdings recht lang. Aber warum nicht die Reise nach Norwegen gleich mit einer kleinen Stippvisite in Dänemark verbinden?

  • Hirtshals – Kristiansand
    Schnellfähre mit nur 2,5 Stunden Fahrzeit. Das Terminal liegt direkt neben dem Bahnhof an der Bahnstrecke Oslo–Stavanger.
    Anbieter: Color Line, Fjord Line
  • Hirtshals – Larvik
    Ebenfalls eine Schnellfähre, Fahrzeit 3:45 Stunden. Das Terminal liegt etwa 2,5 Kilometer vom Bahnhof Larvik entfernt. Von dort bestehen direkte Zugverbindungen nach Drammen und Oslo.
    Anbieter: Color Line
  • Hirtshals – Stavanger – Bergen
    Die Route führt zunächst nach Stavanger (11 Stunden) und weiter bis Bergen (17,5 Stunden). Das Terminal in Stavanger liegt etwas außerhalb im Hafen Risavika und ist per Bus angebunden. In Bergen ist die Ankunft am zentralen Jektevik-Terminal.
    Anbieter: Fjord Line

Finnland

Für uns Zugreisende ist Finnland praktisch eine Insel. Mit der Haparandabahn gibt es zwar auch eine Bahnstrecke, die von Schweden an die finnische Grenze führt. Die ist aber so weit im Norden, dass sie für die Anreise nur untergeordnete Bedeutung hat. Fähren haben für die Anreise nach Finnland schon immer die Hauptrolle gespielt, und das wird sich auch so schnell nicht ändern.

Die klar beste Route führt über Stockholm. Von dort gibt es zwei Fährverbindungen, eine nach Turku und eine nach Helsinki. Die Route nach Turku ist kürzer und schöner. Alternativ könnt ihr euch auch auf eine sehr lange Fahrt direkt von Travemünde nach Helsinki begeben.

Stockholm – Turku

Fahrzeit: 10,5 Stunden

Die Fährfahrt von Stockholm nach Turku ist eine der schönsten überhaupt. Erst geht es durch die Stockholmer Schären, dann durch das Åland-Archipel und schließlich durch den riesigen Schärengarten vor Turku. Die Route ist Finnlands Lebensader und spielt eine zentrale Rolle für den Auto- und Güterverkehr. Gleichzeitig ist sie in Finnland wie Schweden für „Mini-Kreuzfahrten“ beliebt – in 24 Stunden hin und zurück.

Zwischen Stockholm und Turku verkehren zwei Anbieter, die sowohl Tages- als auch Nachtfahrten anbieten. Der Terminal von Viking Line ist mit U-Bahn und Bus erreichbar, Umstieg am Verkehrsknoten „Slussen“. Zusätzlich gibt es einen Shuttlebus der Reederei. Silja Line fährt vom Terminal Värtahamnen nördlich des Stadtzentrums. Dorthin gelangt ihr mit der U-Bahn bis „Gärdet“.

In Turku kommen beide Fähren direkt nebeneinander an der Mündung des Flusses Aurajoki an. Es gibt einen eigenen Hafenbahnhof „Turku satama“, von dem abgestimmt auf die Fährzeiten Züge nach Helsinki und Tampere fahren. Das Stadtzentrum von Turku ist mit dem Bus erreichbar.

Da diese Fähren für mich so etwas wie ein zweites Wohnzimmer sind, hier meine wichtigsten Tipps:

  • Es ist schneller und meist günstiger, mit der Fähre nach Turku zu fahren und das letzte Stück nach Helsinki mit dem Zug zurückzulegen.
  • Die Strecke ist eigentlich zu schön, um sie zu verschlafen. Wenn ihr zum ersten Mal nach Finnland fahrt, legt zumindest eine Richtung am Tag zurück.
  • Auch tagsüber lohnt sich eine Kabine. Sie ist oft erstaunlich günstig und bietet einen guten Rückzugsort und Platz für Gepäck.
  • Viking Line hat die moderneren Schiffe und einen Tick angenehmere Fahrzeiten und ist daher meine Wahl auf dieser Route.

Anbieter:

Stockholm – Helsinki

Fahrzeit: 18 Stunden

Die Fahrt nach Helsinki ist die Komfortvariante. Man ist länger unterwegs, muss sich aber um nichts kümmern und kann die Zeit entspannt an Bord verbringen. Das Seegebiet vor Helsinki ist etwas weniger reizvoll, so kann man sich voll auf das Unterhaltungsangebot an Bord konzentrieren.

Auch zwischen Stockholm und Helsinki verkehren Viking Line und Silja Line parallel. Aufgrund der Länge der Strecke finden die Fahrten allerdings ausschließlich über Nacht statt, Tagesfahrten gibt es nicht. In Helsinki kommen beide Fähren recht zentral an: Silja Line am „Olympiaterminaali“, Viking Line am Katajanokka-Terminal. Beide sind gut an das Straßenbahnnetz angebunden. Zum Marktplatz direkt am Wasser ist es jeweils nur ein kurzer Spaziergang.

Tipp: Während auf der Turku-Strecke Viking Line die Nase vorn hat, ist auf der Linie nach Helsinki Silja Line der Platzhirsch – mit den besseren Schiffen und dem insgesamt runderen Erlebnis.

Anbieter:

Travemünde – Helsinki

Fahrzeit: 30 Stunden

Es ist auch möglich, direkt mit der Fähre von Deutschland nach Finnland zu fahren. Die Fahrt ab Travemünde ist besonders bei allen beliebt, die mit dem Auto nach Finnland reisen und sich die lange Strecke durch Dänemark und Schweden sparen wollen. Die Fähre nimmt aber auch Fußpassagiere mit.

Mit zwei Nächten und einem Tag an Bord ist diese Verbindung sehr lang. Besonders der Seetag auf der offenen Ostsee – ohne Handyempfang und deutsche Fernsehsender – ist je nach Sichtweise entweder eine willkommene Entschleunigung oder schrecklich langweilig.

Meine Einschätzung: Ich habe die Fähre zweimal genommen, als es mitten in der Pandemie keine andere Möglichkeit gab. Als Reisender ohne Auto habe ich mich an Bord etwas fehl am Platz gefühlt, und auf der langen Fahrt durch die im Winter oft raue See bin ich seekrank geworden. Für mich bleibt die Route über Stockholm der unangefochtene Favorit.

Anbieter:

Umeå – Vaasa

Fahrzeit: 3,5 Stunden

Eine weitere Reisemöglichkeit zwischen Schweden und Finnland ist diese Fähre. Sie ist sicher nicht die Verbindung für alle, die einfach nur schnell nach Finnland wollen. Aber für eine Rundreise durch Nordeuropa kann sie durchaus eine interessante Option sein.

Auf der Route verkehrt die Aurora Botnia, die je nach Wochentag ein- oder zweimal täglich zwischen Umeå und Vaasa pendelt. Die Strecke führt durch das Kvarken-Archipel, das zum UNESCO-Weltnaturerbe gehört. Es ist außerdem die nördlichste ganzjährig betriebene Fährverbindung der Welt.

Umeå ist tagsüber oder mit dem Nachtzug direkt aus Stockholm zu erreichen. Das Fährterminal liegt etwa 20 Kilometer außerhalb des Stadtzentrums, es gibt einen Shuttlebus. Auch in Vaasa fährt ein Bus vom Hafen. Der Bahnhof ist von dort aus aber auch zu Fuß erreichbar – der Weg führt landschaftlich reizvoll von der Insel Vaskiluoto ins Stadtzentrum.

Meine Einschätzung: Ich bin einmal mit der Aurora Botnia hinüber nach Schweden gefahren und war begeistert – modernes Schiff ohne Schnickschnack, ein ehrliches und preiswertes Buffet an Bord und dazu eine tolle Landschaft. Geheimtipp!

Anbieter:


Baltikum

Auch wenn sich die Erreichbarkeit des Baltikums mit dem Zug in den letzten Jahren deutlich verbessert hat: Eine Reise nach Litauen, Lettland und Estland auf dem Landweg bleibt ein langwieriges Unterfangen. Warum also nicht entspannt mit der Fähre anreisen?

Von Deutschland aus gibt es zwei direkte Fährverbindungen ins Baltikum. Ebenfalls interessant für Zugreisende ist die kurze Überfahrt zwischen Helsinki und Tallinn, etwa für eine Rundreise um die Ostsee.

Kiel – Klaipėda

Fahrzeit: 21,5 Stunden

Klaipėda, Litauens wichtigste Hafenstadt, liegt am nördlichen Ende der Kurischen Nehrung. Die Stadt lohnt sich nicht nur für einen Städtetrip, sondern ist auch ein guter Ausgangspunkt für eine Reise durchs Baltikum. Die Fährverbindung von Kiel wird sechsmal pro Woche angeboten und verkehrt ausschließlich über Nacht.

In Kiel starten die Fähren vom Ostuferhafen. Dieser liegt etwas außerhalb des Stadtzentrums und ist mit dem Bus erreichbar (Haltestelle „Grenzstraße“). In Klaipėda kommen die Schiffe am zentralen Fährterminal an. Die Altstadt liegt etwa vier Kilometer entfernt, zum Bahnhof am anderen Ende der Stadt geht es in rund 30 Minuten mit dem Bus.

Tipp: Die Fähre Kiel–Klaipėda gehört zu den letzten Verbindungen, auf denen noch Gemeinschaftskabinen angeboten werden. Wer kein Problem damit hat, mit Fremden unterwegs zu sein, kommt auf diese Weise sehr günstig ins Baltikum.

Anbieter:

Travemünde – Liepāja

Fahrzeit: 23 Stunden

Liepāja ist Lettlands Badestadt Nummer eins. Vor den Toren der Stadt liegt ein kilometerlanger Sandstrand, der zu den schönsten der gesamten Ostsee zählt. Die Stadt selbst gilt als eher graue Hafenstadt, hat aber einige interessante Ecken. Im Jahr 2027 ist sie Kulturhauptstadt Europas.

Mit der Fähre ist Liepāja einmal täglich von Travemünde aus erreichbar. Es ist eine lange Fahrt, die in fast 24 Stunden den südlichen Teil der Ostsee einmal von West nach Ost durchquert.

Erst seit 2023 ist Liepāja wieder an das Bahnnetz angeschlossen. Es gibt eine tägliche Zugverbindung nach Riga, die Fahrzeiten sind allerdings nicht auf die Fähre abgestimmt. Deutlich entspannter – und genauso schnell – ist die Weiterfahrt mit den stündlichen Bussen von Lux Express.

Das Fährterminal liegt etwas mehr als zwei Kilometer von Bahnhof und Busbahnhof entfernt und lässt sich zu Fuß erreichen. Direkt an den ÖPNV angeschlossen ist es nicht, die nächste Bushaltestelle liegt rund einen Kilometer entfernt.

Anbieter:

Helsinki – Tallinn

Fahrzeit: 2–2,5 Stunden (Nachtfähren länger)

Der Fährverkehr zwischen Helsinki und Tallinn ist traditionell dicht und wird mit Silja Line, Viking Line und Eckerö Line gleich von drei Anbietern betrieben. Während viele Menschen in Finnland die gut zweistündige Fahrt nutzen, um in Estland günstig einzukaufen, ist die Route auch für Zugreisende bedeutend – erst recht, seit der Landweg über Russland keine Option mehr ist.

Je nach Saison und Wochentag gibt es zwischen Helsinki und Tallinn bis zu ein Dutzend Überfahrten. Obwohl die Fahrt nur kurz ist, werden auch Nachtfahrten mit Kabine angeboten. Um ausreichend Schlaf zu ermöglichen, bleiben diese Fähren mehrere Stunden im Hafen liegen.

In Helsinki nutzen die drei Anbieter jeweils eigene Terminals, die aber alle gut mit der Straßenbahn erreichbar sind. In Tallinn gibt es ein zentrales Fährterminal, das direkt an die malerische Altstadt anschließt. Zum Hauptbahnhof Balti jaam sind es nur etwa zehn Minuten mit der Straßenbahn.

Tipp: Wer nicht viel Gepäck dabeihat und etwas Zeit mitbringt, sollte sich den Spaziergang durch die mittelalterliche Altstadt nicht entgehen lassen. Der Weg zum Bahnhof ist nur rund drei Kilometer lang und führt an den wichtigsten Sehenswürdigkeiten vorbei.

Anbieter:

Stockholm – Tallinn

Fahrzeit: 16 Stunden

Auch Stockholm hat seine direkte Fährverbindung nach Tallinn. Silja Line fährt alle zwei Tage mit ihrem Flaggschiff, der Baltic Queen, direkt von der schwedischen in die estnische Hauptstadt. An den übrigen Tagen wird stattdessen eine Umsteigeverbindung über Helsinki angeboten.

Die Direktfähre verlässt Stockholm am Nachmittag vom Värtahamnen-Terminal und erreicht Tallinn am nächsten Morgen. Beide Terminals sind gut mit U-Bahn beziehungsweise Straßenbahn an Stadtzentrum und Bahnhof angebunden.

Im Sommer bietet auch Viking Line Fahrten zwischen Stockholm und Tallinn an. Dabei handelt es sich um eine Verlängerung der Verbindung nach Helsinki (siehe Finnland-Teil).

Anbieter:

Weitere Fähren ab Schweden

Von Schweden gibt es noch weitere Fährverbindungen ins Baltikum. Für eine Reise aus dem deutschsprachigen Raum spielen sie allerdings meist nur eine Nebenrolle und seien hier deshalb nur kurz erwähnt:

  • Karlshamn – Klaipėda
    Fahrzeit: 14,5 Stunden
    DFDS
  • Trelleborg – Klaipėda
    Fahrzeit: 17 Stunden
    DFDS, TT-Line
  • Nynäshamn – Ventspils
    Fahrzeit: 10,5 Stunden
    Stena Line

Read the whole story
moschlar
2 days ago
reply
Mainz, Deutschland
Share this story
Delete

Why I won't talk about AI anymore.

1 Share
I don’t want to talk about AI anymore. I hate it. Everything about it. I thought a thousand thoughts about it. In the last few months I thought nearly permanently about it, not always because I wanted to. I’m forced to think about it when I have to protect the infrastructure I am trusted with is attacked by them. I have to think about it when I read the news, social media, watch movies, hear music, at work, and a lot more because these abominations are shoved everywhere, and you just can’t evade them anymore.
Read the whole story
moschlar
36 days ago
reply
Mainz, Deutschland
Share this story
Delete

Der Sinn des Lebens

1 Share

Ich bin gerade krank, das bedeutet für mich, ich verbringe Zeit mit mir, meinen Gedanken und einem Buch. Das Buch, das ich gerade lese, heißt 4000 Wochen, ist kurzweilig geschrieben, viel wusste ich aber auch schon bzw. es deckt sich mit meiner Meinung. Darum soll es hier aber nicht gehen. Gegen Ende des Buchs gibt es ein hervorragendes Kapitel unter dem Titel „Die »Dem-Kosmos-ist’s-egal-Therapie«“, das einen Gedanken bei mir entfacht hat.

Read the whole story
moschlar
45 days ago
reply
Mainz, Deutschland
Share this story
Delete

Stamp It! All Programs Must Report Their Version

1 Share

Recently, during a production incident response, I guessed the root cause of an outage correctly within less than an hour (cool!) and submitted a fix just to rule it out, only to then spend many hours fumbling in the dark because we lacked visibility into version numbers and rollouts… 😞

This experience made me think about software versioning again, or more specifically about build info (build versioning, version stamping, however you want to call it) and version reporting. I realized that for the i3 window manager, I had solved this problem well over a decade ago, so it was really unexpected that the problem was decidedly not solved at work.

In this article, I’ll explain how 3 simple steps (Stamp it! Plumb it! Report it!) are sufficient to save you hours of delays and stress during incident response.

Why are our versioning standards so low?!

Every household appliance has incredibly detailed versioning! Consider this dishwasher:

a dishwasher, with many precise bits of version information

(Thank you Feuermurmel for sending me this lovely example!)

I observed a couple household appliance repairs and am under the impression that if a repair person cannot identify the appliance, they would most likely refuse to even touch it.

So why are our standards so low in computers, in comparison? Sure, consumer products are typically versioned somehow and that’s typically good enough (except for, say, USB 3.2 Gen 1×2!). But recently, I have encountered too many developer builds that were not adequately versioned!

Software Versioning

Unlike a physical household appliance with a stamped metal plate, software is constantly updated and runs in places and structures we often cannot even see.

Let’s dig into what we need to increase our versioning standard!

Usually, software has a name and some version number of varying granularity:

  • Chrome
  • Chrome 146
  • Chrome 146.0.7680.80
  • Chrome f08938029c887ea624da7a1717059788ed95034d-refs/branch-heads/7680_65@{#34}

All of these identify the Chrome browser on my computer, but each at different granularity.

All are correct and useful, depending on the context. Here’s an example for each:

  1. “This works in Chrome for me, did you test in Firefox?”
  2. “Chrome 146 contains broken middle-click-to-paste-and-navigate”
  3. “I run Chrome 146.0.7680.80 and cannot reproduce your issue”
  4. “Apply this patch on top of Chrome f08938029c887ea624da7a1717059788ed95034d-refs/branch-heads/7680_65@{#34} and follow these steps to reproduce: […]”

After creating the i3 window manager, I quickly learned that for user support, it is very valuable for programs to clearly identify themselves. Let me illustrate with the following case study.

Case Study: i3’s --version and --moreversion

When running i3 --version, you will see output like this:

% i3 --version
i3 version 4.24 (2024-11-06) © 2009 Michael Stapelberg and contributors

Each word was carefully deliberated and placed. Let me dissect:

  1. i3 version 4.24: I could have shortened this to i3 4.24 or maybe i3 v4.24, but I figured it would be helpful to be explicit because i3 is such a short name. Users might mumble aloud “What’s an i-3-4-2-4?”, but when putting “version” in there, the implication is that i3 is some computer thing (→ a computer program) that exists in version 4.24.
  2. (2024-11-06) is the release date so that you can immediately tell if “4.24” is recent.
  3. © 2009 Michael Stapelberg signals when the project was started and who is the main person behind it.
  4. and contributors gives credit to the many people who helped. i3 was never a one-person project; it was always a group effort.

When doing user support, there are a couple of questions that are conceptually easy to ask the affected user and produce very valuable answers for the developer:

  1. Question: “Which version of i3 are you using?”
    • Since i3 is not a typical program that runs in a window (but a window manager / desktop environment), there is no Help → About menu option.
    • Instead, we started asking: What is the output of i3 --version?
  2. Question: “Are you reporting a new issue or a preexisting issue? To confirm, can you try going back to the version of i3 you used previously?”. The technical terms for “going back” are downgrade, rollback or revert.
    • Depending on the Linux distribution, this is either trivial or a nightmare.
    • With NixOS, it’s trivial: you just boot into an older system “generation” by selecting that version in the bootloader. Or you revert in git, if your configs are version-controlled.
    • With imperative Linux distributions like Debian Linux or Arch Linux, if you did not take a file system-level snapshot, there is no easy and reliable way to go back after upgrading your system. If you are lucky, you can just apt install the older version of i3. But you might run into dependency conflicts (“version hell”).
    • I know that it is possible to run older versions of Debian using snapshot.debian.org, but it is just not very practical, at least when I last tried.
  3. Can you check if the issue is still present in the latest i3 development version?
    • Of course, I could also try reproducing the user issue with the latest release version, and then one additional time on the latest development version.
    • But this way, the verification step moves to the affected user, which is good because it filters for highly-motivated bug reporters (higher chance the bug report actually results in a fix!) and it makes the user reproduce the bug twice, figuring out if it’s a flaky issue, hard-to-reproduce, if the reproduction instructions are correct, etc.
    • A natural follow-up question: “Does this code change make the issue go away?” This is easy to test for the affected user who now has a development environment.

Based on my experiences with asking these questions many times, I noticed a few patterns in how these debugging sessions went. In response, I introduced another way for i3 to report its version in i3 v4.3 (released in September 2012): a --moreversion flag! Now I could ask users a small variation of the first question: What is the output of i3 --moreversion? Note how this also transfers well over spoken word, for example at a computer meetup:

Michael: Which version are you using?

User: How can I check?

Michael: Run this command: i3 --version

User: It says 4.24.

Michael: Good, that is recent enough to include the bug fix. Now, we need more version info! Run i3 --moreversion please and tell me what you see.

When you run i3 --moreversion, it does not just report the version of the i3 program you called, it also connects to the running i3 window manager process in your X11 session using its IPC (interprocess communication) interface and reports the running i3 process’s version, alongside other key details that are helpful to show the user, like which configuration file is loaded and when it was last changed:

% i3 --moreversion
Binary i3 version:  4.24 (2024-11-06) © 2009 Michael Stapelberg and…
Running i3 version: 4.24 (2024-11-06) (pid 2521)
Loaded i3 config:
  /home/michael/.config/i3/config (main)
  (last modified: 2026-03-15T23:09:27 CET, 1101585 seconds ago)

The i3 binary you just called:
/nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24/bin/i3
The i3 binary you are running: i3

This might look like a lot of detail on first glance, but let me spell out why this output is such a valuable debugging tool:

  1. Connecting to i3 via the IPC interface is an interesting test in and of itself. If a user sees i3 --moreversion output, that implies they will also be able to run debugging commands like (for example) i3-msg -t get_tree > /tmp/tree.json to capture the full layout state.

  2. During a debugging session, running i3 --moreversion is an easy check to see if the version you just built is actually effective (see the Running i3 version line).

    • Note that this is the same check that is relevant during production incidents: verifying that effectively running matches supposed to be running versions.
  3. Showing the full path to the loaded config file will make it obvious if the user has been editing the wrong file. If the path alone is not sufficient, the modification time (displayed both absolute and relative) will flag editing the wrong file.

I use NixOS, BTW, so I automatically get a stable identifier (0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24) for the specific build of i3.

% ls -l $(which i3)
lrwxrwxrwx 1 root root 58 1970-01-01 01:00 /run/current-system/sw/bin/i3
-> /nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24/bin/i3

To see the build recipe (“derivation” in Nix terminology) which produced this Nix store output (0zn9r4263…-i3-4.24), I can run nix derivation show:

% nix derivation show /nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24
{
  "/nix/store/z7ly4kvgixf29rlz01ji4nywbajfifk4-i3-4.24.drv": {
[…]
Click here to expand the full nix derivation show output if you are curious
% nix derivation show /nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24
{
  "/nix/store/z7ly4kvgixf29rlz01ji4nywbajfifk4-i3-4.24.drv": {
    "args": [
      "-e",
      "/nix/store/l622p70vy8k5sh7y5wizi5f2mic6ynpg-source-stdenv.sh",
      "/nix/store/shkw4qm9qcw5sc5n1k5jznc83ny02r39-default-builder.sh"
    ],
    "builder": "/nix/store/6ph0zypyfc09fw6hlc1ygjvk2hv4j9vd-bash-5.3p3/bin/bash",
    "env": {
      "NIX_MAIN_PROGRAM": "i3",
      "__structuredAttrs": "",
      "buildInputs": "/nix/store/58q0dn2lbm2p04qmds0aymwdd1fr5j67-libxcb-1.17.0-dev /nix/store/3fcfw014z5i05ay1ag0hfr6p81mb1kzw-libxcb-keysyms-0.4.1-dev /nix/store/2cdrqvd3av1dmxna9xjqv1jccibpvg6m-libxcb-util-0.4.1-dev /nix/store/256alp82fhdgbxx475dp7mk8m29y53rh-libxcb-wm-0.4.2-dev /nix/store/nr44nfhj48abr3s6afqy1fjq4qmr23lz-xcb-util-xrm-1.3 /nix/store/ml4cfhhw6af6qq6g3dn7g5j5alrnii88-libxkbcommon-1.11.0-dev /nix/store/6hnzjg09fd5xkkrdj437wyaj952nlg45-libstartup-notification-0.12 /nix/store/9m0938zahq7kcfzzix4kkpm8d1iz3nmq-libx11-1.8.12-dev /nix/store/vz5gd0rv0m2kjca50gacz0zq9qh7i8xf-pcre2-10.46-dev /nix/store/334cvqpqc9f0plv0aks71g352w6hai0c-libev-4.33 /nix/store/6s3fw10c0441wv53bybjg50fh8ag1561-yajl-2.1.0-unstable-2024-02-01 /nix/store/d6aw2004h90dwlsfcsygzzj4pzm1s31a-libxcb-cursor-0.1.6-dev /nix/store/84mhqfj9amzyvxhp37yh3b0zd8sq0a7p-perl-5.40.0 /nix/store/l6bslkrp59gaknypf1jrs5vbb2xmcwym-pango-1.57.0-dev /nix/store/7s7by82nq8bahsh195qr0mnn9ac8ljmm-perl5.40.0-AnyEvent-I3-0.19 /nix/store/9ml0p4x1cx5k1lla91bxgramc0amsfkf-perl5.40.0-X11-XCB-0.20 /nix/store/67j1sx7qcn6f7qvq1kh3z8i5mpajgq3r-perl5.40.0-IPC-Run-20231003.0 /nix/store/859x84mz38bcq0r7hwksk4b5apcsmf2w-perl5.40.0-ExtUtils-PkgConfig-1.16 /nix/store/q1qydg6frfpq9jkhnymfsjzf71x9jswr-perl5.40.0-Inline-C-0.82",
      "builder": "/nix/store/6ph0zypyfc09fw6hlc1ygjvk2hv4j9vd-bash-5.3p3/bin/bash",
      "checkPhase": "runHook preCheck\n\ntest_failed=\n# \"| cat\" disables fancy progress reporting which makes the log unreadable.\n./complete-run.pl -p 1 --keep-xserver-output | cat || test_failed=\"complete-run.pl returned $?\"\nif [ -z \"$test_failed\" ]; then\n  # Apparently some old versions of `complete-run.pl` did not return a\n  # proper exit code, so check the log for signs of errors too.\n  grep -q '^not ok' latest/complete-run.log && test_failed=\"test log contains errors\" ||:\nfi\nif [ -n \"$test_failed\" ]; then\n  echo \"***** Error: $test_failed\"\n  echo \"===== Test log =====\"\n  cat latest/complete-run.log\n  echo \"===== End of test log =====\"\n  false\nfi\n\nrunHook postCheck\n",
      "cmakeFlags": "",
      "configureFlags": "",
      "debug": "/nix/store/20rgxn6fpywd229vka9dnjiaprypxirh-i3-4.24-debug",
      "depsBuildBuild": "",
      "depsBuildBuildPropagated": "",
      "depsBuildTarget": "",
      "depsBuildTargetPropagated": "",
      "depsHostHost": "",
      "depsHostHostPropagated": "",
      "depsTargetTarget": "",
      "depsTargetTargetPropagated": "",
      "doCheck": "1",
      "doInstallCheck": "",
      "mesonFlags": "-Ddocs=true -Dmans=true",
      "name": "i3-4.24",
      "nativeBuildInputs": "/nix/store/x06h0jfzv99c3dmb8pj8wbmy0v9wj6bd-pkg-config-wrapper-0.29.2 /nix/store/pcdnznc797nmf9svii18k3c5v22sqihs-make-shell-wrapper-hook /nix/store/nzg469dkg5dj7lv4p50pi8zmwzxx73hr-meson-1.9.1 /nix/store/rlcn0x0j22nbhhf8wfp8cwfxgh65l82r-ninja-1.13.1 /nix/store/hs4pgi40k5nbl0fpf0jx8i5f6zrdv63v-install-shell-files /nix/store/84mhqfj9amzyvxhp37yh3b0zd8sq0a7p-perl-5.40.0 /nix/store/xiqlw1h0i6a6v59skrg9a7rg3qpanqy7-asciidoc-10.2.1 /nix/store/300facd5m37fwqrypjcikn09vqs488zv-xmlto-0.0.29 /nix/store/yk7avh2szvm6bi5dwgzz4c2iciaipj2p-docbook-xml-4.5 /nix/store/d5qdxn0rjl9s7xfc1rca33gya0fhcvkm-docbook-xsl-nons-1.79.2 /nix/store/2y1r1cpza3lpk7v6y9mf75ak0pswilwi-find-xml-catalogs-hook /nix/store/r989dk196nl9frhnfsa1lb7knhbyjxw6-separate-debug-info.sh /nix/store/xlhipdkyqksxvp73cznnij5q6ilbbqd9-xorg-server-21.1.21-dev /nix/store/i8nxxmw5rzhxlx3n12s3lvplwwap6mpc-xvfb-run-1+g87f6705 /nix/store/a198i9cnhn6y5cajkdxg0hhcrmalazjr-xdotool-3.20211022.1 /nix/store/b4dnjyq2i4kjg8xswkjd7lwfcdps94j8-setxkbmap-1.3.4 /nix/store/cxdbw6iqj1a1r69wb55xl5nwi7abfllb-xrandr-1.5.3 /nix/store/5k4mv2a1qrciv12wywlkgpslc6swyv58-which-2.23",
      "out": "/nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24",
      "outputs": "out debug",
      "patches": "",
      "pname": "i3",
      "postInstall": "wrapProgram \"$out/bin/i3-save-tree\" --prefix PERL5LIB \":\" \"$PERL5LIB\"\nfor program in $out/bin/i3-sensible-*; do\n  sed -i 's/which/command -v/' $program\ndone\n\ninstallManPage man/*.1\n",
      "postPatch": "patchShebangs .\n\n# This testcase generates a Perl executable file with a shebang, and\n# patchShebangs can't replace a shebang in the middle of a file.\nif [ -f testcases/t/318-i3-dmenu-desktop.t ]; then\n  substituteInPlace testcases/t/318-i3-dmenu-desktop.t \\\n    --replace-fail \"#!/usr/bin/env perl\" \"#!/nix/store/84mhqfj9amzyvxhp37yh3b0zd8sq0a7p-perl-5.40.0/bin/perl\"\nfi\n",
      "propagatedBuildInputs": "",
      "propagatedNativeBuildInputs": "",
      "separateDebugInfo": "1",
      "src": "/nix/store/qx48i7zf9n69yla8gfbif6dskysk0l1w-source",
      "stdenv": "/nix/store/43dbh9z6v997g6njz4yqmcrj26zic9ds-stdenv-linux",
      "strictDeps": "",
      "system": "x86_64-linux",
      "version": "4.24"
    },
    "inputDrvs": {
      "/nix/store/0h97zzsaf4ggiiwi0rbdjl3fzjj8vhj0-meson-1.9.1.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/0r073sy0685h3gycpl8kpkgmv5p87rw4-libxcb-1.17.0.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/0rjr80q4lpigwjwaxw089wcrrag7p46m-xmlto-0.0.29.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/14wsbyw3j1h9blcxr16c9663w0piq0p2-bash-5.3p3.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/165y3ip2cqlnqd6qrgh6lzklv21xy11w-make-shell-wrapper-hook.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/1abxvpwsry6q5pijb2j91aryh2ilp929-pango-1.57.0.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/2sjcj6l2959dvd5vlicmkf1sdr0hwqx5-perl5.40.0-ExtUtils-PkgConfig-1.16.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/3jnvpbpi95g6zp8vjq1qafh20lz6kwi3-perl5.40.0-X11-XCB-0.20.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/45szhbhybqh4fkcpmx7sqpcrpwpadvgv-pkg-config-wrapper-0.29.2.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/4r5bd9g98fq40hjbfc7sbnp42jhnzg5h-yajl-2.1.0-unstable-2024-02-01.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/4yw0g3zqw4gn1szw8bqrvgmz5b6qm8s5-stdenv-linux.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/53gin0imc257fibkbyvl0jsi0pm1zvbl-docbook-xml-4.5.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/54q42ddy9jb24v4mbx0f19faqqsw5jga-libxkbcommon-1.11.0.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/56dg95jlnwp6kkifyqh94f548r5cha9b-xrandr-1.5.3.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/6srgz2k17vc6x85s3paccdbgg9rv0bia-asciidoc-10.2.1.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/7xpmbw1xzzwxcd1rnx6qid7zhqnzq3jh-setxkbmap-1.3.4.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/87b385i529h64dzrycf16ksv0jcbzs29-libev-4.33.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/9l94a5gr0wbhaq6zyl30wpqygp1cffrx-pcre2-10.46.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/b8hhyx6rpy47hkbq5wlhrvfrfv3yn7j8-xvfb-run-1+g87f6705.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/bxrnxv90lrpvq06rja47986h057rhwcc-libxcb-cursor-0.1.6.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/cgdz2idkz91w2k7hpb2dymv80938cz9w-libxcb-wm-0.4.2.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/ddvlvaj43mls902nay7ddjrg01d6c2la-perl-5.40.0.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/ddxlvkpjlg6ycayb6az23ldjdr21xlnf-which-2.23.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/ds5ss96inhkj9x2gbd7shinvbiid6v6b-xorg-server-21.1.21.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/f0yqdlwz2vwsx51wlgmi9pjqpdhbprkx-ninja-1.13.1.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/gm613dry4hkv26m7ml49fq60z8p0r0gf-perl5.40.0-IPC-Run-20231003.0.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/h3sjzf7hg9ghbh4hzdg6c4byfky2fjng-libx11-1.8.12.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/j5ji7yjwizrma9h72h2pqgi8ir6ah6q8-libstartup-notification-0.12.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/k2jxg4mck2f4pqlisp6slwhyd3pva8wz-source.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/n19ll9p9ivkni2y9l9i2rypyi5gi8z58-perl5.40.0-Inline-C-0.82.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/nm7v937f2z7srs54idjwc7sl6azc1slj-xdotool-3.20211022.1.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/qzg3b7p4gf4izfjbkc42bjyrvp8vz99k-xcb-util-xrm-1.3.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/rjmh0kp3w170bii9i57z5anlshzm2gll-install-shell-files.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/rrsm8jbqqf58k30cm2lxmgk43fkxsgqp-find-xml-catalogs-hook.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/s4wl1ny41k50rkxw0x0wdjf9l5mjqyv0-libxcb-util-0.4.1.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/vxckbgl5kwf5ikz0ma0fkavsnh683ry0-libxcb-keysyms-0.4.1.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "dev"
        ]
      },
      "/nix/store/xxb7x7j73p3sxf03hb1hzaz588avd3yw-docbook-xsl-nons-1.79.2.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      },
      "/nix/store/yik59jhh69af5fcvddmxlhfwya69pnzw-perl5.40.0-AnyEvent-I3-0.19.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      }
    },
    "inputSrcs": [
      "/nix/store/l622p70vy8k5sh7y5wizi5f2mic6ynpg-source-stdenv.sh",
      "/nix/store/r989dk196nl9frhnfsa1lb7knhbyjxw6-separate-debug-info.sh",
      "/nix/store/shkw4qm9qcw5sc5n1k5jznc83ny02r39-default-builder.sh"
    ],
    "name": "i3-4.24",
    "outputs": {
      "debug": {
        "path": "/nix/store/20rgxn6fpywd229vka9dnjiaprypxirh-i3-4.24-debug"
      },
      "out": {
        "path": "/nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24"
      }
    },
    "system": "x86_64-linux"
  }

Unfortunately, I am not aware of a way to go from the derivation to the .nix source, but at least one can check that a certain source results in an identical derivation.

Developer builds

The versioning I have described so far is sufficient for most users, who will not be interested in tracking intermediate versions of software, but only the released versions.

But what about developers, or any kind of user who needs more precision?

When building i3 from git, it reports the git revision it was built from, using git-describe(1) :

~/i3/build % git describe
4.25-23-g98f23f54
~/i3/build % ninja
[110/110] Linking target i3
~/i3/build % ./i3 --version
i3 version 4.25-23-g98f23f54 © 2009 Michael Stapelberg and contributors

A modified working copy gets represented by a + after the revision:

~/i3/build % echo '// dirty working copy' >> ../src/main.c && ninja
[104/104] Linking target i3bar
~/i3/build % ./i3 --version
i3 version 4.25-23-g98f23f54+ © 2009 Michael Stapelberg and contributors

Reporting the git revision (or VCS revision, generally speaking) is the most useful choice.

This way, we catch the following common mistakes:

  • People build from the wrong revision.
  • People build, but forget to install.
  • People install, but their session does not pick it up (wrong location?).

Most Useful: Stamp The VCS Revision

As we have seen above, the single most useful piece of version information is the VCS revision. We can fetch all other details (version numbers, dates, authors, …) from the VCS repository.

Now, let’s demonstrate the best case scenario by looking at how Go does it!

Go always stamps! 🥳

Go has become my favorite programming language over the years, in big part because of the good taste and style of the Go developers, and of course also because of the high-quality tooling:

Therefore, I am pleased to say that Go implements the gold standard with regard to software versioning: it stamps VCS buildinfo by default! 🥳 This was introduced in Go 1.18 (March 2022):

Additionally, the go command embeds information about the build, including build and tool tags (set with -tags), compiler, assembler, and linker flags (like -gcflags), whether cgo was enabled, and if it was, the values of the cgo environment variables (like CGO_CFLAGS).

Both VCS and build information may be read together with module information using go version -m file or runtime/debug.ReadBuildInfo (for the currently running binary) or the new debug/buildinfo package.

What does this mean in practice? Here is a diagram for the common case: building from git:

diagram showing going from git repository to binary by invoking go build / go install

This covers most of my hobby projects!

Many tools I just go install, or CGO_ENABLED=0 go install if I want to easily copy them around to other computers. Although, I am managing more and more of my software in NixOS.

When I find a program that is not yet fully managed, I can use gops and the go tool to identify it:

root@ax52 ~ % nix run nixpkgs#gops
2573594 1       dcs-package-importer  go1.26.1 /nix/store/clby54zb003ibai8j70pwad629lhqfly-dcs-unstable/bin/dcs-package-importer
2573576 1       dcs-source-backend    go1.26.1 /nix/store/clby54zb003ibai8j70pwad629lhqfly-dcs-unstable/bin/dcs-source-backend
2573566 1       debiman               go1.25.5 /srv/man/bin/debiman
[…]
root@ax52 ~ % nix run nixpkgs#go -- version -m /srv/man/bin/debiman
/srv/man/bin/debiman: go1.25.5
  path	github.com/Debian/debiman/cmd/debiman
  mod	github.com/Debian/debiman	v0.0.0-20251230101540-ac8f5391b43b+dirty
  […]
  dep	pault.ag/go/debian	v0.18.0	h1:nr0iiyOU5QlG1VPnhZLNhnCcHx58kukvBJp+dvaM6CQ=
  dep	pault.ag/go/topsort	v0.1.1	h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4=
  build	-buildmode=exe
  build	-compiler=gc
  build	DefaultGODEBUG=containermaxprocs=0,decoratemappings=0,tlssha1=1,updatemaxprocs=0,x509sha256skid=0
  build	CGO_ENABLED=0
  build	GOARCH=amd64
  build	GOOS=linux
  build	GOAMD64=v1
  build	vcs=git
  build	vcs.revision=ac8f5391b43bc1a9dbdc99f6179e2fb7d7414a04
  build	vcs.time=2025-12-30T10:15:40Z
  build	vcs.modified=true
root@ax52 ~ %

It’s very cool that Go does the right thing by default!

Systems that consist of 100% Go software (like my gokrazy Go appliance platform) are fully stamped! For example, the gokrazy web interface shows me exactly which version and dependencies went into the gokrazy/rsync build on my scan2drive appliance.

Despite being fully stamped, note that gokrazy only shows the module versions, and no VCS buildinfo, because it currently suffers from the same gap as Nix:

gokrazy scan2drive rsync

Go Version Reporting

For the gokrazy packer, which follows a rolling release model (no version numbers), I ended up with a few lines of Go code (see below) to display a git revision, no matter if you installed the packer from a Go module or from a git working copy.

The code either displays vcs.revision (the easy case; built from git) or extracts the revision from the Go module version of the main module (BuildInfo.Main.Version):

What are the other cases? These examples illustrate the scenarios I usually deal with:

source (built from) buildinfo (stamped into program)
directory (no git) module (devel)
Go module module v0.3.1-0.20260105212325-5347ac5f5bcb
directory (git) module v0.0.0-20260131174001-ccb1d233f2a4+dirty
vcs.revision=ccb1d233f2a43e9118b9146b3c9a5ded1efb7551
vcs.time=2026-01-31T17:40:01Z
vcs.modified=true
diagram showing the two cases with go build info stamping: building from a git checkout or installing from a Go module
Go code to programmatically read the version
package version

import (
	"runtime/debug"
	"strings"
)

func readParts() (revision string, modified, ok bool) {
	info, ok := debug.ReadBuildInfo()
	if !ok {
		return "", false, false
	}
	settings := make(map[string]string)
	for _, s := range info.Settings {
		settings[s.Key] = s.Value
	}
	// When built from a local VCS directory, we can use vcs.revision directly.
	if rev, ok := settings["vcs.revision"]; ok {
		return rev, settings["vcs.modified"] == "true", true
	}
	// When built as a Go module (not from a local VCS directory),
	// info.Main.Version is something like v0.0.0-20230107144322-7a5757f46310.
	v := info.Main.Version // for convenience
	if idx := strings.LastIndexByte(v, '-'); idx > -1 {
		return v[idx+1:], false, true
	}
	return "<BUG>", false, false
}

func Read() string {
	revision, modified, ok := readParts()
	if !ok {
		return "<not okay>"
	}
	modifiedSuffix := ""
	if modified {
		modifiedSuffix = " (modified)"
	}

	return "https://github.com/gokrazy/tools/commit/" + revision + modifiedSuffix
}

This is what it looks like in practice:

% go install github.com/gokrazy/tools/cmd/gok@latest
% gok --version
https://github.com/gokrazy/tools/commit/8ed49b4fafc7

But a version built from git has the full revision available (→ you can tell them apart):

% (cd ~gokrazy/../tools && go install ./cmd/...)
% gok --version
https://github.com/gokrazy/tools/commit/ba6a8936f4a88ddcf20a3b8f625e323e65664aa6 (modified)

VCS rev with NixOS

When packaging Go software with Nix, it’s easy to lose Go VCS revision stamping:

  1. Nix fetchers like fetchFromGitHub are implemented by fetching an archive (.tar.gz) file from GitHub — the full .git repository is not transferred, which is more efficient.
  2. Even if a .git repository is present, Nix usually intentionally removes it for reproducibility: .git directories contain packed objects that change across git gc runs (for example), which would break reproducible builds (different hash for the same source).

So the fundamental tension here is between reproducibility and VCS stamping.

Luckily, there is a solution that works for both: I created the stapelberg/nix/go-vcs-stamping Nix overlay module that you can import to get working Go VCS revision stamping by default for your buildGoModule Nix expressions!

diagram from Git repo to go build without and with my go-vcs-stamping overlay workaround

The Nix Go build situation in detail

Tip: If you are not a Nix user, feel free to skip over this section. I included it in this article so that you have a full example of making VCS stamping work in the most complicated environments.


Packaging Go software in Nix is pleasantly straightforward.

For example, the Go Protobuf generator plugin protoc-gen-go is packaged in Nix with <30 lines: official nixpkgs protoc-gen-go package.nix. You call buildGoModule, supply as src the result from fetchFromGitHub and add a few lines of metadata.

But getting developer builds fully stamped is not straightforward at all!

When packaging my own software, I want to package individual revisions (developer builds), not just released versions. I use the same buildGoModule, or buildGoLatestModule if I need the latest Go version. Instead of using fetchFromGitHub, I provide my sources using Flakes, usually also from GitHub or from another Git repository. For example, I package gokrazy/bull like so:

{
  pkgs,
  pkgs-unstable,
  bullsrc,
  ...
}:

# Use buildGoLatestModule to build with Go 1.26
# even before NixOS 26.05 Yarara is released
# (NixOS 25.11 contains Go 1.25).
pkgs-unstable.buildGoLatestModule {
  pname = "bull";
  version = "unstable";

  src = bullsrc;

  # Needs changing whenever `go mod vendor` changes,
  # i.e. whenever go.mod is updated to use different versions.
  vendorHash = "sha256-sU5j2dji5bX2rp+qwwSFccXNpK2LCpWJq4Omz/jmaXU=";
}

The bullsrc comes from my flake.nix:

Click here to expand the full flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
    nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";

    disko = {
      url = "github:nix-community/disko";
      # Use the same version as nixpkgs
      inputs.nixpkgs.follows = "nixpkgs";
    };

    stapelbergnix.url = "github:stapelberg/nix";

    zkjnastools.url = "github:stapelberg/zkj-nas-tools";

    configfiles = {
      url = "github:stapelberg/configfiles";
      flake = false; # repo is not a flake
    };

    bullsrc = {
      url = "github:gokrazy/bull";
      flake = false;
    };

    sops-nix = {
      url = "github:Mic92/sops-nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs =
    {
      nixpkgs,
      nixpkgs-unstable,
      disko,
      stapelbergnix,
      zkjnastools,
      bullsrc,
      configfiles,
      sops-nix,
      ...
    }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs {
        inherit system;
        config.allowUnfree = false;
      };
      pkgs-unstable = import nixpkgs-unstable {
        inherit system;
        config.allowUnfree = false;
      };
    in
    {
      nixosConfigurations.keep = nixpkgs.lib.nixosSystem {
        inherit system;
        inherit pkgs;
        specialArgs = { inherit configfiles; };
        modules = [
          disko.nixosModules.disko
          sops-nix.nixosModules.sops
          ./configuration.nix
          stapelbergnix.lib.userSettings
          stapelbergnix.lib.zshConfig
          # Use systemd for network configuration
          stapelbergnix.lib.systemdNetwork
          # Use systemd-boot as bootloader
          stapelbergnix.lib.systemdBoot
          # Run prometheus node exporter in tailnet
          stapelbergnix.lib.prometheusNode
          zkjnastools.nixosModules.zkjbackup

          {
            nixpkgs.overlays = [
              (final: prev: {
                bull = import ./bull-pkg.nix {
                  pkgs = final;
                  pkgs-unstable = pkgs-unstable;
                  inherit bullsrc;
                };
              })
            ];
          }

        ];
      };
      formatter.${system} = pkgs.nixfmt-tree;
    };
}

Go stamps all builds, but it does not have much to stamp here:

  • We build from a directory, not a Go module, so the module version is (devel).
  • The stamped buildinfo does not contain any vcs information.

Here’s a full example of gokrazy/bull:

% go version -m \
  /nix/store/z3y90ck0fp1wwd4scljffhwxcrxjhb9j-bull-unstable/bin/bull
/nix/store/z3y90ck0fp1wwd4scljffhwxcrxjhb9j-bull-unstable/bin/bull: go1.26.1
        path    github.com/gokrazy/bull/cmd/bull
        mod     github.com/gokrazy/bull (devel) 
        dep     github.com/BurntSushi/toml      v1.4.1-0.20240526193622-a339e1f7089c    
        dep     github.com/fsnotify/fsnotify    v1.8.0  
        dep     github.com/google/renameio/v2   v2.0.2  
        dep     github.com/yuin/goldmark        v1.7.8  
        dep     go.abhg.dev/goldmark/wikilink   v0.5.0  
        dep     golang.org/x/image      v0.23.0 
        dep     golang.org/x/sync       v0.10.0 
        dep     golang.org/x/sys        v0.28.0 
        build   -buildmode=exe
        build   -compiler=gc
        build   -trimpath=true
        build   CGO_ENABLED=0
        build   GOARCH=amd64
        build   GOOS=linux
        build   GOAMD64=v1

To fix VCS stamping, add my goVcsStamping overlay to your nixosSystem.modules:

{
  nixpkgs.overlays = [
    stapelbergnix.overlays.goVcsStamping
  ];
}

(If you are using nixpkgs-unstable, like I am, you need to apply the overlay in both places.)

After rebuilding, your Go binaries should newly be stamped with vcs buildinfo:

% go version -m /nix/store/z8mgsf10pkc6dgvi8pfnbb7cs23pqfkn-bull-unstable/bin/bull
[…]
  build   vcs=git
  build   vcs.revision=c0134ef21d37e4ca8346bdcb7ce492954516aed5
  build   vcs.time=2026-03-22T08:32:55Z
  build   vcs.modified=false

Nice! 🥳 But… how does it work? When does it apply? How do you know how to fix your config?

I’ll show you the full diagram first, and then explain how to read it:

a big diagram showing all the ways from .nix expression to a stamped binary or a binary where VCS info got lost

There are 3 relevant parts of the Nix stack that you can end up in, depending on what you write into your .nix files:

  1. Fetchers. These are what Flakes use, but also non-Flake use-cases.
  2. Fixed-output derivations (FOD). This is how pkgs.fetchgit is implemented, but the constant hash churn (updating the sha256 line) inherent to FODs is annoying.
  3. Copiers. These just copy files into the Nix store and are not git-aware.

For the purpose of VCS revision stamping, you should:

  • Avoid the Copiers! If you use Flakes:
    • ❌ do not use url = "/home/michael/dcs" as a Flake input
    • ✅ use url = "git+file:///home/michael/dcs" instead for git awareness
  • I avoid the fixed-output derivation (FOD) as well.
    • Fetching the git repository at build time is slow and inefficient.
    • Enabling leaveDotGit, which is needed for VCS revision stamping with this approach, is even more inefficient because a new Git repository must be constructed deterministically to keep the FOD reproducible.

Hence, we will stick to the left-most column: fetchers.

Unfortunately, by default, with fetchers, the VCS revision information, which is stored in a Nix attrset (in-memory, during the build process), does not make it into the Nix store, hence, when the Nix derivation is evaluated and Go compiles the source code, Go does not see any VCS revision.

My stapelberg/nix/go-vcs-stamping Nix overlay module fixes this, and enabling the overlay is how you end up in the left-most lane of the above diagram: the happy path, where your Go binaries are now stamped!

My workaround: Nix git buildinfo overlay

How does the go-vcs-stamping overlay work? It functions as an adapter between Nix and Go:

  • Nix tracks the VCS revision in the .rev in-memory attrset.
  • Go expects to find the VCS revision in a .git repository, accessed via .git/HEAD file access and git(1) commands.

So the overlay implements 3 steps to get Go to stamp the correct info:

  1. It synthesizes a .git/HEAD file so that Go’s vcs.FromDir() detects a git repository.
  2. It injects a git command into the PATH that implements exactly the two commands used by Go and fails loudly on anything else (in case Go updates its implementation).
  3. It sets -buildvcs=true in the GOFLAGS environment variable.

For the full source, see go-vcs-stamping.nix.

The clean fix

See Go issue #77020 and Go issue #64162 for a cleaner approach to fixing this gap: allowing package managers to invoke the Go tool with the correct VCS information injected.

This would allow Nix (or also gokrazy) to pass along buildinfo cleanly, without the need for workarounds like my go-vcs-stamping adapter.

At the time of writing, issue #77020 does not seem to have much traction and is still open.

Conclusion: Stamp it! Plumb it! Report it!

My argument is simple:

Stamping the VCS revision is conceptually easy, but very important!

For example, if the production system from the incident I mentioned had reported its version, we would have saved multiple hours of mitigation time!

Unfortunately, many environments only identify the build output (useful, but orthogonal), but do not plumb the VCS revision (much more useful!), or at least not by default.

Your action plan to fix it is just 3 simple steps:

  1. Stamp it! Include the source VCS revision in your programs.
    • This is not a new idea: i3 builds include their git-describe(1) revision since 2012!
  2. Plumb it! When building / packaging, ensure the VCS revision does not get lost.
    • My “VCS rev with NixOS” case study section above illustrates several reasons why the VCS rev could get lost, which paths can work and how to fix the missing plumbing.
  3. Report it! Make your software print its VCS revision on every relevant surface, for example:
    • Executable programs: Report the VCS revision when run with --version
      • For Go programs, you can always use go version -m
    • Services and batch jobs: Include the VCS revision in the startup logs.
    • Outgoing HTTP requests: Include the VCS revision in the User-Agent
    • HTTP responses: Include the VCS revision in a header (internally)
    • Remote Procedure Calls (RPCs): Include the revision in RPC metadata
    • User Interfaces: Expose the revision somewhere visible for debugging.

Implementing “version observability” throughout your system is a one-day high-ROI project.

With my Nix example, you saw how the VCS revision is available throughout the stack, but can get lost in the middle. Hopefully my resources help you quickly fix your stack(s), too:

Now go stamp your programs and data transfers! 🚀

Read the whole story
moschlar
47 days ago
reply
Mainz, Deutschland
Share this story
Delete

Teenager vs. Fast-Boomer

1 Share

Die Schlagzeile „15-Jähriger klaut Linienbus – und fährt Freundin zur Schule“ im vergangenen Monat ist mir im Kopf geblieben. Ich fand das irgendwie lustig und herzig – vielleicht auch weil alles gut ging, der Bus keine Kratzer hatte und keine Menschen verletzt wurden. So lässt sich dieser Vorfall leicht romantisieren und natürlich bin ich sehr froh, dass nicht eines meiner Kinder diese Idee hatte.

Allerdings dachte ich, als ich das erste Mal von diesem Vorfall las: „Teenager, man muss sie einfach lieben!“ und das meine ich nicht ironisch.
Ich liebe sehr vieles am Teenagergemüt. Die überschäumende Energie, der Experimentierdrang, der Glaube, dass es kein Limit gibt, dass alles möglich ist. Die Scheißegalhaltung manchen Dingen gegenüber und der Wille alles anders zu machen und auf den Kopf zu stellen. Ich liebs einfach.

Und hier passt das Wort Ambiguitätstoleranz auch so schön. Also die Fähigkeit Widersprüche und mehrdeutige Informationen zu ertragen. Denn natürlich finde ich all das auch manchmal gleichzeitig anstrengend und nervig und es macht mir angst. Denn wenn Jugendliche manchmal glauben „Ach, das sind nur 6 Meter, da spring ich einfach runter“, dann kann das natürlich auch richtig schief gehen.

Als Mutter fühle ich mich oft herausgefordert und kann mich schlecht zurückhalten nicht ständig meine Bedenken zu etwas zu äußern. Auf der anderen Seite arbeite ich aber auch wirklich hart daran, nicht alles zu kommentieren, schlecht zu machen oder zu entmutigen, denn nur weil ICH etwas nicht kann, nur weil ICH etwas für unwahrscheinlich halte, heißt das ja noch lange nicht, dass eine andere Person, das nicht kann oder möglich machen kann.

Das Gegenteil vom Teenager-Mindset ist ja dieses Behördenklischee: „Das haben wir noch nie so gemacht! Also machen wir es für alle Zeiten auf diese und keine andere Weise!“ (oder fangen gar nicht erst an) und das geht mir sehr auf den Senkel. Wenn man Möglichkeiten auslotet und sich nur Bedenkenträgern gegenüber sieht, die alle Argumente kennen warum etwas NICHT funktioniert.

Vielleicht finde ich das auch so ermüdend weil in mir vieles schon so geschwächt und schlaff ist. Vielleicht habe ich Angst, dass Bedenkenträger meinen letzten Hauch Lebensfrohsinn zertreten und vielleicht finde ich es deswegen so toll mich mit Teenagern zu unterhalten, für die alles lowkey und easy finden.
Ich wünsche mir auch von Herzen innerlich zu spüren „10 km joggen? Das schaffe ich ohne Vorbereitung!“, „Abschlussprüfung Englisch? Da fange ich zwei Tage vorher an zu lernen.“, „Job? Da gehe ich morgen hin und frage“. Wie gut muss sich das bitte anfühlen?

Eines meiner Kinder hört gerne „Muse“ und ich kanns total verstehen. Ich hab mir „Origin of Symmetry“ angehört und es kaum 5 Lieder ausgehalten. Dieser Soundteppich erschlägt mich regelrecht, all die Ebenen und dann all die Gefühle, die die Stimme und die Texte übertragen. Puh. Aber ich bin ja auch schon alt und alles an mir ist ausgedünnt: meine Haut, mein Nervenkostüm, meine Haarzellen im Innenohr.

Ich bin dann froh und traurig gleichzeitig. Froh, weil ich nicht mehr so viele Gefühle haben muss und nicht alles so intensiv sein muss und traurig weil es eben nicht so ist und alles relativ gleichbleibend in ruhigen Gewässern läuft.

Wahrscheinlich ist es für meine Kinder unglaublich nervig, aber ich höre ihnen gerne zu und sie sind ein Faszinosum. Ich bestaune und bewundere sie und ich bin wirklich voller Liebe für das, was sie geworden sind. Es wird mir schwer fallen irgendwann ein Alltagsleben ohne sie zu finden. Denn irgendwann werden sie ja ausziehen und dann lese ich nur noch von Teenagern in Zeitungen und amüsiere mich über den Ideenreichtum und das Selbstbewusstsein. Denn ich würde mir nicht zutrauen einen Bus von Mainz nach Karlsruhe zu fahren.

Der Beitrag Teenager vs. Fast-Boomer erschien zuerst auf Das Nuf Advanced.

Read the whole story
moschlar
51 days ago
reply
Mainz, Deutschland
Share this story
Delete

The World That Was

1 Comment and 2 Shares

It’s easy to be overwhelmed by the world. I mean, look at … everything. Massive ongoing wars everywhere, Fascism on the rise, exploding inequality. Shit is fucked up and more fucked up on a global scale than it ever was in my life time (I was born in 1979). And with the media landscape and notifications and 24 hour news it’s hard to not feel overwhelmed. Every morning when waking up is basically:

And it is important to be informed. To at least try to see what is going on in order to decide where one can make a difference or maybe at least help? Someone? Anyone?

But this is also no way to live. For a bunch of different reasons. I think given the state of the world it’s fair to let certain crises go into the background (without going full ignorance): You just mentally cannot dive into every crisis all the time. Not just because you don’t have the hours in the day but also because it will destroy your mind.

I have this tendency to believe that if I just dig for more information and understand, that if I can make sense of something, I will feel better and it will create some form of path towards resolution. That it would allow me to send a letter to a politician or support an organization or write or do something that can help turn things around. I believe that knowledge and understanding creates agency. Which isn’t 100% false but in the way I apply it is basically delusional.

And I do that because I am scared. I am scared by the consequences of the chaos. I’ve learned enough about history to understand that when shit hits the fan it’s rarely the powerful and wealthy who suffer the most. That it starts hurting at the bottom and then quickly moves up. And that scares me. Not in the abstract but in my bones. Even more now that I have a son who I just want to be able to live a life full of joy and love.

But being scared is not all I feel (even though it is a big part of it). I am grieving.

I realized that a few days ago when I took some time off of the news and all that. I was exhausted and burned out and took a walk. And understood that I was literally grieving. I was sad for the structure of the world that I see crashing down.

And don’t get me wrong. The structure wasn’t perfect. Or even great. We built a world order based on exploitation of the planet and each other. With some good things bolted to it here or there, some remnants of socialist and human rights thinking. Certain safety nets, certain conventions. It wasn’t much, but it was something. And now that they are being dismantled in record time I am grieving for those tiny things.

Because while that system was in place it did – at least to me, and maybe that was naive – feel as if we could use it as a platform to build something better on. Drive back the inequality and exploitation through collective action. The road to “fully automated luxury space communism” was still very long but it felt like there might be a floor to it all. And that floor was still too low and did not include everyone, probably a minority even. But from my privileged position as someone living in Germany it felt like a foundation to build on. A consensus.

And I miss it. It hurts to see it being killed. To see that in fact there is no consensus that includes any commitment – even a surface level one – to human rights and the will to build something better than “billionaires can get even richer while the world is burning”.

This is not a feeling I am planning to dwell on for too long. But I think it’s important that during the storm of news and notifications and whatever we sometimes take the time to understand how that makes us feel and why?

I am grieving because I had felt like there was sort of a “emergency break” kind of thing that would ensure things would be going too bad. And coming from a family where I inherited my parents’ fear of the threat of downwards social mobility that gave me a lot of emotional support. It was about more about a feeling than it was about facts.

It’s important to understand how the world makes you feel. And share it. Otherwise your emotions are gonna catch up with you at some point.

Now is the time to get back to it. Even if the rules-based order that I grew up in and relied on all my life is crumbling, maybe we can redirect that momentum towards something better. Or at least stop some fascists. “Pessimism of the intellect, optimism of the will” and all that.

Read the whole story
tante
113 days ago
reply
I wrote a bit about grief. Not for a person but the world that was.
Berlin/Germany
moschlar
112 days ago
reply
Mainz, Deutschland
Share this story
Delete
Next Page of Stories