docker-cpg: docker cp mit Wildcards

Ein Wrapper um docker cp, der Wildcards, mehrere Quellen und Ausschlüsse ergänzt und auch mit gestoppten Containern und Images ohne Shell funktioniert.

Das Problem

docker cp kopiert genau einen Pfad und expandiert keine Wildcards:

$ docker cp "web:/var/log/app/*.log" logs/
Error response from daemon: Could not find the file /var/log/app/*.log in container web

Die übliche Umgehung startet tar im Container und entpackt den Stream auf dem Host: docker exec CONTAINER sh -c 'tar -cf - /path/*.log' | tar -xf -. Dafür braucht es einen laufenden Container, der sh und tar enthält. Bei gestoppten Containern sowie bei Distroless- und FROM scratch-Images scheitert sie deshalb.

Der Wunsch ist alt:

Wie es funktioniert

docker-cpg nutzt den Tar-Stream, den docker cp bereits anbietet. Beim Herauskopieren nimmt es das feste Verzeichnis vor der ersten Wildcard (/var/log/app in /var/log/app/*.log), streamt es mit docker cp CONTAINER:/var/log/app/. - und schreibt nur die Tar-Einträge, die passen. Im Container läuft nichts. Der Container darf also gestoppt sein, und das Image braucht keine Shell. Beim Hineinkopieren werden die lokalen Quellen in einen Tar-Stream gepackt und mit docker cp - CONTAINER:DIR gesendet. Eine einzelne Quelle ohne Wildcards, Ausschlüsse oder andere Optionen geht unverändert an docker cp.

Die Muster folgen der Shell: *, ? und [...] passen innerhalb einer Pfadkomponente, ** passt auf beliebig viele Verzeichnisse, und Wildcards passen auf Namen mit führendem Punkt nur, wenn das Muster selbst mit einem Punkt beginnt. --exclude überspringt Dateien und Verzeichnisse, --parents behält den vollen Quellpfad unter dem Ziel, und - als Ziel schreibt einen Tar-Stream auf stdout. Das Werkzeug ist in Python ohne Abhängigkeiten geschrieben. In ~/.docker/cli-plugins verlinkt, läuft es als docker cpg und reicht globale Flags wie --context an seine eigenen docker-Aufrufe weiter.

Dieses Beispiel aus dem README kopiert aus einem gestoppten Container in einen Tar-Stream:

$ docker stop web
$ docker cpg "web:/var/log/app/*.log" - | tar tv
docker-cpg: copied 3 match(es), 3 file(s) to -
-rw-r--r-- 0/0               7 2026-10-30 05:09 access.log
-rw-r--r-- 0/0               6 2026-10-30 05:09 debug.log
-rw-r--r-- 0/0               6 2026-10-30 05:09 error.log

Tests

Die Testsuite umfasst 26 Tests. Die Unit-Tests decken das Musterabgleichen (*, ?, Zeichenklassen, **, versteckte Namen), Ausschlussmuster, das Parsen der Container-Argumente und Bedienfehler ab. Die Integrationstests bauen in Python ein kleines Root-Dateisystem, importieren es mit docker import und erzeugen daraus Container, die nie gestartet werden. Das Image enthält weder eine Shell noch tar. Die Fälle umfassen symbolische Links, ein schreibgeschütztes Verzeichnis, einen Hardlink, zwei Treffer mit gleichem Namen, den Exit-Status 1 ohne Treffer, Probeläufe, Tar-Ausgabe auf stdout und das Hineinkopieren mehrerer Quellen. Ein symbolischer Link, der im Ziel schon existiert, wird ersetzt. Das Werkzeug schreibt nicht durch ihn hindurch.

Das CI führt die Suite auf den ubuntu-24.04-Runnern von GitHub mit Python 3.9, 3.12 und 3.13 gegen Docker 28.0 aus. Dort schlagen die Integrationstests fehl, statt übersprungen zu werden, wenn kein Docker-Daemon erreichbar ist. Ein weiterer Schritt installiert das Werkzeug als CLI-Plugin, prüft, dass docker --help den Befehl cpg auflistet, kopiert web:/logs/**/*.log aus einem busybox-Container und macht einen Probelauf über docker --context default cpg.

Grenzen

  • Beim Herauskopieren wird das ganze feste Verzeichnis vor der ersten Wildcard gestreamt und auf dem Host gefiltert. "web:/var/log/*.log" liest ganz /var/log samt Unterverzeichnissen, "web:/**/*.conf" das gesamte Dateisystem des Containers.
  • Mit Wildcard ist das Ziel immer ein Verzeichnis. Haben zwei Treffer denselben Namen, wird der erste kopiert und die übrigen werden mit einer Warnung übersprungen, sofern --parents nicht gesetzt ist.
  • Beim Hineinkopieren muss das Ziel ein existierendes Verzeichnis sein (eine Regel von docker cp -).
  • Kopieren zwischen zwei Containern, Klammererweiterung und das Auswerten einer .dockerignore gibt es nicht.
  • Getestet ist es unter Linux mit Docker 27.0 und 28.0. --docker podman ist nicht getestet.

Code und Installationsanleitung liegen unter github.com/Jwrede/docker-cpg.