„Container-Image“ ist im Sprachgebrauch fast zum Synonym für „Docker“ geworden — dabei ist das Format längst von Docker entkoppelt und in der Open Container Initiative
(OCI) standardisiert. Dieser Beitrag erklärt, was das konkret bringt, warum meine Build-Pipeline deshalb mit Skopeo und Crane statt docker push arbeitet, und wie ich meine bisherige Harbor-Registry gegen das deutlich schlankere
Zot
getauscht habe.
Was OCI eigentlich ist
Hinter dem Kürzel stecken drei aufeinander aufbauende Spezifikationen, die zusammen das gesamte Lebenszyklus-Vokabular einer Registry definieren:
- Die Image Spec
beschreibt, wie ein Image aufgebaut ist — Layer, Config, und ein Manifest, das beides über ihre
sha256-Digests zusammenbindet. Alles ist content-addressable: Der Digest ist der Inhalt. - Die Distribution Spec definiert die HTTP-API, über die diese Blobs und Manifeste geschoben und gezogen werden. Jede konforme Registry spricht dieselbe Sprache.
- Die Artifact- und Referrers-Erweiterungen erlauben schließlich, beliebige Inhalte neben einem Image abzulegen und zu verknüpfen — Helm-Charts, SBOMs, WASM-Module und, für mich besonders relevant, cosign -Signaturen.
Der praktische Gewinn dieser Standardisierung ist Werkzeugfreiheit. Weil das Format und die API offen sind, ist niemand mehr an einen bestimmten Daemon oder Anbieter gebunden — eine Eigenschaft, die meine gesamte Build- und Registry-Architektur trägt.
Warum nicht mehr Harbor
Harbor
ist eine ausgewachsene Enterprise-Registry — und genau das wurde ihm bei mir zum Verhängnis. Eine Harbor-Installation ist kein Dienst, sondern ein Verbund: core, portal, jobservice, eine eingebettete Registry, dazu PostgreSQL und ein Redis-kompatibler Cache, optional Notary und Trivy als eigene Komponenten. Das ist viel beweglicher Teil für „ich möchte meine selbstgebauten Images ablegen“.
Dazu kam ein handfester, sehr konkreter Blocker: Mein Cluster läuft auf ARM64-Hardware, und für genau diese Architektur fehlte Harbor zum damaligen Zeitpunkt offizieller Support — der entsprechende Merge stand noch aus. Eine Enterprise-Registry, die auf meiner Plattform gar nicht erst sauber startet, ist die denkbar schlechteste Wahl.
Zot: nur OCI, dafür ganz
Zot ist das genaue Gegenteil: ein einzelner Go-Prozess, der ausschließlich die OCI-Spezifikationen implementiert — keine proprietären Erweiterungen, kein Datenbank-Cluster, sauberer ARM64-Support. Die Konfiguration ist eine einzige JSON-Datei, und schon die Storage-Sektion zeigt, dass man es hier mit einer ernstzunehmenden Registry zu tun hat: Garbage Collection, ein periodischer Integritäts-scrub und eine deklarative Retention-Policy:
"storage": {
"gc": true,
"gcInterval": "6h",
"retention": {
"policies": [{
"repositories": ["**"],
"deleteUntagged": true,
"keepTags": [{ "mostRecentlyPushedCount": 10, "pushedWithin": "720h" }]
}]
}
}
Erreichbar ist die Registry unter oci.this-is-fine.io, eingebunden über das Envoy-Gateway wie jeder andere Dienst.
Der Fallstrick: kein docker push
Die größte Umstellung gegenüber dem gewohnten Workflow: In meiner Build-Pipeline kommt kein docker push zum Einsatz, sondern
Skopeo
und
Crane
. Das hat handfeste Gründe:
- Daemonlos.
docker pushsetzt einen laufenden Docker-Daemon voraus. Skopeo und Crane sprechen die OCI-Distribution-API direkt über HTTP — kein Daemon, kein Socket, keine Root-Rechte auf dem CI-Runner. Das passt zu einem flüchtigen, zustandslosen Build-Agenten. - Multi-Arch ohne Buildx-Krücke. Ich baue
amd64undarm64getrennt und füge sie erst nachgelagert zu einem Multi-Arch-Index zusammen. Das ist exakt Cranes Spezialgebiet —crane index appendbaut das Index-Manifest aus den einzelnen Architektur-Manifesten, ohne dass ein Daemon je beide gleichzeitig sehen muss:skopeo copy "docker-archive:img-arm64.tar" "docker://oci.this-is-fine.io/${img}:build-arm64" crane index append --manifest …:build-amd64 --manifest …:build-arm64 --tag …:${TAG} - OCI-Artefakte statt nur Images. Die cosign-Signaturen, die meine Images absichern, sind selbst OCI-Artefakte, die über die Referrers-API am Image hängen. Skopeo und Crane behandeln diese Manifest-Typen nativ; der klassische Docker-Workflow hat dafür kein sauberes Vokabular.
Kurz: Sobald man die Registry als das begreift, was sie ist — ein content-addressable OCI-Store, nicht ein „Docker-Ding“ —, sind daemonlose, OCI-native Werkzeuge die natürliche Wahl.
Zugriff: Menschen per OIDC, Maschinen per htpasswd
Authentifizierung trenne ich strikt nach Akteur. Menschen melden sich am Web-UI über OIDC bei Pocket-ID an; Maschinen nutzen statische htpasswd-Credentials. Die Autorisierung regelt Zots accessControl über Gruppen:
"accessControl": {
"groups": {
"admins": { "users": ["admin", "buildbot", "clawbot"] },
"users": { "users": ["fetchbot"] }
},
"repositories": { "**": { "policies": [{ "groups": ["users"], "actions": ["read"] }], "defaultPolicy": [] } },
"adminPolicy": { "groups": ["admins"], "actions": ["read", "create", "update", "delete"] }
}
Das Prinzip dahinter ist „least privilege“ in Reinform:
Schreibrechte hat einzig die Build-Pipeline (buildbot); der Pull-Bot des Clusters (fetchbot) darf ausschließlich lesen, und anonyme Zugriffe laufen gegen eine leere defaultPolicy ins Leere. Wer Images baut, und wer sie nur konsumiert, ist damit kryptografisch wie autorisatorisch sauber getrennt.
Eingebaute Sicherheit: Trivy und cosign
Zwei Erweiterungen runden das Bild ab. Die Suche bindet Trivy
ein und scannt die abgelegten Images täglich gegen die CVE-Datenbank — ich sehe also direkt im UI, ob ein Image bekannte Schwachstellen mitschleppt. Und die trust-Erweiterung verifiziert cosign-Signaturen, sodass die signierten Images aus meiner Pipeline auch in der Registry-Oberfläche als vertrauenswürdig ausgewiesen werden:
"extensions": {
"search": { "cve": { "updateInterval": "24h", "trivy": { … } } },
"trust": { "enable": true, "cosign": true },
"scrub": { "enable": true, "interval": "24h" }
}
Fazit
Der Tausch von Harbor gegen Zot ist dasselbe Muster, das sich durch mein ganzes Lab zieht: das spezialisierte, schlanke Werkzeug schlägt die eierlegende Wollmilchsau, sobald man deren Features nicht wirklich braucht. Geblieben ist eine Registry, die genau die OCI-Spezifikationen spricht, sauber auf ARM64 läuft, Signaturen und Schwachstellen kennt — und sich mit Skopeo und Crane so anfühlt, wie das Format eigentlich gemeint war.
