Wer in einem Docker-Build einen Token braucht, greift schnell zu ARG und --build-arg. Das funktioniert, hat aber einen Haken: der Wert landet in der Build-Historie und oft auch in einem Layer. Wer das Image später zieht, kann ihn auslesen. BuildKit löst das mit Secret-Mounts, die nur zur Build-Zeit sichtbar sind und in keinem Layer stehen. Ich zeige, wie das konkret aussieht.
Warum Build-Args und COPY Secrets durchreichen
Ein ARG ist kein Geheimnis. Er steht im Klartext in den Image-Metadaten und lasst sich mit einem einzigen Befehl wieder herausholen. Dasselbe gilt, wenn eine Datei mit Zugangsdaten in einen fruhen Layer kopiert und in einem spateren Schritt geloscht wird: der Layer mit der Datei bleibt erhalten, das spatere rm uberdeckt ihn nur.
docker build --build-arg NPM_TOKEN=geheim -t app .docker history --no-trunc app | grep NPM_TOKEN# der Token steht im Klartext in der History
Der Mechanismus: –mount=type=secret
BuildKit hangt ein Secret nur fur die Dauer eines einzelnen RUN-Schritts in das Dateisystem ein, standardmassig unter /run/secrets/. Nach dem Schritt ist es weg und wird nie Teil eines Layers. Die erste Zeile im Dockerfile aktiviert die passende Syntax.
# syntax=docker/dockerfile:1.7FROM node:22-alpineWORKDIR /appCOPY package*.json ./RUN --mount=type=secret,id=npm_token \ NPM_TOKEN=$(cat /run/secrets/npm_token) npm ciCOPY . .
Der Token wird innerhalb des RUN-Schritts aus /run/secrets/npm_token gelesen und als Umgebungsvariable an npm ci gegeben. Er existiert nur in diesem einen Prozess.
Build aufrufen
Das Secret wird beim Build ubergeben, entweder aus einer Datei oder direkt aus einer Umgebungsvariable des Build-Hosts. Der Wert taucht danach nirgends im Image auf.
# aus einer Dateidocker build --secret id=npm_token,src=$HOME/.npm_token -t app .# aus einer Umgebungsvariableexport NPM_TOKEN=geheimdocker build --secret id=npm_token,env=NPM_TOKEN -t app .
SSH-Zugriff als Sonderfall
Fur private Git-Repos im Build gibt es einen eigenen Mount-Typ, der den SSH-Agent des Hosts durchreicht, ohne den Schlussel selbst ins Image zu bringen.
# syntax=docker/dockerfile:1.7FROM alpine/gitRUN --mount=type=ssh git clone git@github.com:firma/privates-repo.git
docker build --ssh default -t app .
Prufen, ob wirklich nichts im Image liegt
Zwei Kontrollen reichen. Die History darf das Secret nicht enthalten, und ein Blick in die Layer bestatigt, dass keine Datei mit dem Wert zuruckbleibt.
docker history --no-trunc app | grep -i token # sollte leer seindocker run --rm app printenv | grep -i token # sollte leer sein
Wer es genauer will, nutzt dive und geht die Layer einzeln durch. Wenn der Token weder in History noch in einem Layer noch in der Laufzeit-Umgebung steht, ist er sauber aus dem Image draussen.
Grenzen
Secret-Mounts schutzen das fertige Image, nicht den Build-Host. Wer dort die Umgebungsvariable oder die Token-Datei liest, hat den Wert weiterhin. Der Build-Runner braucht also die gleiche Least-Privilege-Disziplin wie jeder andere Ort, an dem Zugangsdaten liegen. Und ein Secret-Mount ersetzt keine Rotation: ein einmal geleakter Token bleibt gultig, bis er gedreht wird.
Wie Secret-Handling im Build in eine durchgehende Pipeline passt, zusammen mit Signieren und Scan, habe ich auf digital-business.blog ausfuhrlicher beschrieben.
Hinterlasse einen Kommentar