Das Problem
Docker Compose setzt configs.*.file und secrets.*.file als Bind-Mounts
um, und ein Bind-Mount wird auf dem Docker-Host aufgelöst. Spricht Compose mit
einem entfernten Host (DOCKER_HOST=ssh://..., ein docker context oder
tcp://...), muss die Datei dort unter demselben absoluten Pfad liegen. Sonst
startet der Container nicht:
Error response from daemon: invalid mount config for type "bind":
bind source path does not exist: /home/me/project/secrets/db_password.txt
Das Problem ist dokumentiert:
- docker/compose#11867,
"Support remote DOCKER_HOST by copying configs and secrets files instead of
bind mounting them", ist seit Mai 2024 offen und hat 45 Reaktionen und 50
Kommentare. Der Maintainer befürwortet die Richtung, möchte aber eine
Opt-in-Syntax, weil Kopieren bestehendes Verhalten ändert. Fünf Pull
Requests, die es umsetzten, wurden ungemergt geschlossen. Ein Kommentar
wünscht sich ausdrücklich einen Weg, die Dateien nicht von Hand in die
Eigenschaft
contentübertragen zu müssen. - Frühere Meldungen desselben Fehlers sind #7857, #11141 und #7110.
- Per Bind-Mount eingebundene Configs und Secrets ignorieren
uid,gidundmode(#9648, 30 Reaktionen).
Die Umgehungen im Thread sind: die Dateien auf den Docker-Host kopieren und dort absolute Pfade verwenden, pro Host eine eigene Override-Datei pflegen oder die Dateien in ein Image einbauen.
Wie es funktioniert
Compose hat bereits einen Codepfad, der entfernt funktioniert: Configs mit
content: sowie Configs und Secrets mit environment: werden vor dem Start
über die Docker-API in den Container geschrieben.
compose-inline, ein Python-Werkzeug
ohne Abhängigkeiten, leitet Dateien auf diesen Pfad um. Es liest das aufgelöste Modell mit
docker compose config --format json. Jede Config, deren Datei auf dem
lokalen Rechner existiert, wird zu content:, wobei $ maskiert wird, weil
Compose das Modell erneut interpoliert. Secrets und Configs, die kein gültiges
UTF-8 sind, werden zu environment:-Einträgen, deren Werte nur in der Umgebung
des Kindprozesses stehen. Secret-Werte landen deshalb nie auf der Platte. Mit
--bind-files werden Bind-Mounts einzelner Dateien im Projektordner zu Configs,
mit den Dateirechten als mode.
Compose bezieht Config-Inhalte nicht in den Hash des Dienstes ein. Eine
geänderte Datei würde beim nächsten up den Container also nicht neu
erzeugen. compose-inline hängt deshalb an jeden betroffenen Dienst ein Label
mit einem Hash der übergebenen Inhalte. Das umgeschriebene Modell schreibt es
in eine private temporäre Datei und führt damit den gewünschten
docker compose-Befehl aus.
Das ist das Beispiel aus dem README, ein Projekt mit einer Config, einem Secret
und einer per Bind-Mount eingebundenen nginx.conf, ausgeführt gegen einen
Host ohne die Projektdateien (Pfade im README gekürzt):
$ export DOCKER_HOST=ssh://deploy@server
$ docker compose up -d
Container app-web-1 Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /home/me/app/secrets/db_password.txt
$ compose-inline --bind-files --verbose up -d --wait
compose-inline: inlined config site from /home/me/app/site.conf (87 bytes)
compose-inline: inlined secret db_password from /home/me/app/secrets/db_password.txt (22 bytes)
compose-inline: inlined bind mount /home/me/app/nginx.conf:/etc/nginx/nginx.conf of service web (57 bytes)
Container app-web-1 Created
Container app-web-1 Started
Container app-web-1 Healthy
Tests
18 Unit-Tests decken das Umschreiben und die Argumentverarbeitung ab:
maskierte Dollarzeichen, Secrets und Nicht-UTF-8-Configs über die Umgebung,
abgelehnte NUL-Bytes und zu große Secrets, fehlende Dateien und Verzeichnisse,
die Bind-Mounts bleiben, --bind-files und ein Hash-Label, das sich mit dem
Inhalt ändert.
Der End-to-End-Test läuft gegen einen docker:27-dind-Container, der über TCP
erreicht wird und die Dateien des Checkouts nicht sieht. Er prüft, dass
einfaches docker compose mit "bind source path does not exist" scheitert,
dass compose-inline die Config, die eingebundene Datei und das Secret mit den
Rechten 600 und 640 liefert, dass up den Container nur nach einer Änderung
einer Datei neu erzeugt und dass ein Config-Pfad, der nur auf dem Docker-Host
existiert, ein Bind-Mount bleibt. Das CI führt die Unit-Tests mit Python 3.9,
3.12 und 3.13 aus und den End-to-End-Test mit Compose v2.38.2 und v2.24.7. Die
ältere Version liest einen mode-String als Dezimalzahl, deshalb schreibt das
Werkzeug die Rechte als Ganzzahl.
Grenzen
- Dateien werden einmal kopiert, beim Erzeugen des Containers. Eine lokale
Änderung erreicht den Container erst beim nächsten
up. - Compose verweigert das Kopieren in Dienste mit
read_only: true. - Kopierte Dateien gehören root, sofern Config oder Secret nicht
uidundgidsetzen. Configs haben standardmäßig den Modus0444. - Verzeichnisse und Pfade, die lokal nicht existieren, bleiben Bind-Mounts.
--bind-fileswandelt nur Dateien im Projektordner um. - Dateien mit NUL-Bytes werden abgelehnt. Secrets laufen über die Umgebung, die Linux auf 128 KiB pro Wert begrenzt.
- Das Label
com.docker.compose.project.config_filesder Container zeigt auf die gelöschte temporäre Datei. - Getestet ist es mit Compose v5.0.0, v2.38.2 und v2.24.7 gegen Docker 27.5.1. Mit Podman ist es nicht getestet.
Code und Installationsanleitung liegen unter github.com/Jwrede/compose-inline.