SSD Nodes Learn 🎉 VPS ab $5.50/Monat
Anleitungen Matt ConnorVon Matt Connor

Docker Compose: build oder image auf einem VPS

image ruft einen veröffentlichten Tag ab, build erstellt lokal. Erfahren Sie, warum compose up Dockerfile-Änderungen ignoriert und wie Sie das beheben.

Docker Compose: build oder image – die Kurzantwort

In einer Docker-Compose-Datei gibt image: ein Image an, das aus einer Registry abgerufen wird. build: weist Compose an, auf diesem Rechner anhand einer Dockerfile ein Image zu erstellen. Wenn Sie nur image: setzen, ruft Compose diesen Tag ab und startet ihn. Wenn Sie nur build: setzen, erstellt Compose das Image lokal und vergibt dafür einen Namen, der sich aus dem Projektnamen und dem Servicenamen ableitet. Wenn Sie beide setzen, erstellt Compose das Image lokal und versieht das Ergebnis mit dem Namen aus image:. So erstellen Sie ein Image und können es unter einem selbst gewählten Namen pushen.

Das ist der gesamte Unterschied. Im Folgenden wird erläutert, was das im Betrieb auf einem Server bedeutet. Dabei wird vorausgesetzt, dass Docker Engine und das Compose-Plugin bereits installiert sind. Docker auf einem VPS ausführen behandelt diesen Teil.

Die drei Formen vollständig

Ein veröffentlichtes Tag abrufen und ausführen. Zu keinem Zeitpunkt ist ein Dockerfile beteiligt.

services:
  web:
    image: nginx:1.27
    restart: unless-stopped
    ports:
      - "80:80"

Aus einem Dockerfile im aktuellen Verzeichnis bauen. Außer dem in FROM angegebenen Basis-Image wird nichts abgerufen.

services:
  web:
    build: .
    restart: unless-stopped
    ports:
      - "80:80"

Lokal bauen und das Ergebnis taggen. docker compose push kann dieses exakte Tag anschließend an eine Registry senden.

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: registry.example.com/acme/web:1.4.2
    restart: unless-stopped
    ports:
      - "80:80"

context ist das an den Builder gesendete Verzeichnis. dockerfile wird relativ zu diesem Kontext aufgelöst. Daher ist context: . zusammen mit dockerfile: docker/prod.Dockerfile normal und korrekt. Führen Sie docker compose images aus, um den Image-Namen und die Image-ID hinter jedem Service-Container anzuzeigen. So lässt sich am schnellsten überprüfen, welche dieser drei Formen Sie tatsächlich verwendet haben.

Warum erstellt docker compose up das Image nach einer Änderung an der Dockerfile nicht neu?

Weil up prüft, ob das Image vorhanden ist, nicht ob es aktuell ist.

Wenn Compose einen Dienst mit einem Abschnitt build: startet, sucht es das Image im lokalen Image-Speicher. Wenn dort bereits ein Image mit diesem Namen vorhanden ist, verwendet Compose es. Compose liest Ihre Dockerfile nicht ein, vergleicht Ihre Quelldateien nicht und prüft auch keine Zeitstempel. Die Compose-Spezifikation definiert diese Regel über das Attribut pull_policy. Standardmäßig wird ein Image nur erstellt, wenn es fehlt. Vorhanden gilt als ausreichend.

Sie bearbeiten also app.py, führen docker compose up -d aus, sehen in der Ausgabe von Compose, dass der Container läuft, und stellen fest, dass weiterhin der alte Code bereitgestellt wird. Es ist kein Fehler aufgetreten, daher wurde auch keine Warnung ausgegeben. Dies ist die häufigste Ursache für die Meldung „Meine Änderung wurde nicht übernommen“ bei Compose. Der entscheidende Hinweis ist das Statuswort, das Compose neben dem Containernamen ausgibt: Einen von Compose ersetzten Container meldet Compose als recreated oder started. Einen Container, den Compose unverändert lässt, meldet es als running.

Zwei Prüfungen schaffen Klarheit. docker compose images gibt die Image-ID aus, die jeder Container verwendet. Notieren Sie sie vor dem Deployment und vergleichen Sie sie danach. docker image ls enthält eine Spalte CREATED. Ein Image, das vor Ihrem letzten Commit erstellt wurde, ist unabhängig von der Ausgabe des Deployment-Skripts veraltet.

Welche Flags erzwingen einen Neuaufbau

  • docker compose up -d --build führt zuerst den Build aus und erstellt anschließend jeden Container neu, dessen Image geändert wurde. Dieses Flag ist in den meisten Fällen gemeint.
  • docker compose build web erstellt ein einzelnes Service-Image und startet nichts. Verwenden Sie anschließend docker compose up --no-deps -d web, um nur diesen Container zu ersetzen und den restlichen Stack weiterlaufen zu lassen.
  • docker compose build --no-cache web verwirft alle gecachten Layer und erstellt das Image ab der ersten Anweisung neu.
  • docker compose build --pull versucht, in FROM eine neuere Version des Basis-Images abzurufen. Ein veränderliches Tag wie node:22 verwendet dadurch den aktuellen Inhalt statt der Kopie, die Sie im März heruntergeladen haben.
  • docker compose up -d --force-recreate erstellt Container aus dem bereits verwendeten Image neu. Es führt niemals einen Build aus. Wer dieses Flag verwendet, obwohl --build gemeint war, landet häufig in einer Sackgasse.

Sie können diese Entscheidung auch in die Datei verlagern. Nach der Compose-Spezifikation bedeutet pull_policy: build, dass Compose das Image erstellt und bei einem bereits vorhandenen Image neu erstellt. Jeder up führt dann einen Build aus. Das ist auf einem Laptop sinnvoll, auf einem Server jedoch selten.

services:
  web:
    build: .
    image: registry.example.com/acme/web:dev
    pull_policy: build

Eine weitere Wechselwirkung sollten Sie kennen. docker compose pull versucht auch, Images für Services abzurufen, die einen Abschnitt build enthalten. Wenn dieser Abruf fehlschlägt, weist der Befehl darauf hin, dass das Image stattdessen erstellt werden muss. Übergeben Sie --ignore-buildable, um diese Services ohne Meldung zu überspringen.

Wie der Build-Cache Ihre Deploy-Zeit bestimmt

Jede Anweisung in einem Dockerfile erzeugt eine Layer. Der Builder verwendet eine gecachte Layer erneut, wenn diese Anweisung und ihre Eingaben unverändert sind. Für COPY sind die Eingaben der Inhalt der kopierten Dateien. Sobald eine Layer den Cache nicht trifft, werden alle nachfolgenden Layer neu erstellt. Jede Layer wird auf dem Dateisystem aufgebaut, das die vorherige Layer erzeugt hat.

Diese eine Regel entscheidet, ob Ihr Deploy Sekunden oder Minuten dauert. Ordnen Sie das Dockerfile von selten geänderten zu häufig geänderten Inhalten.

FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]

npm ci steht über COPY . .. Wenn Sie eine Quelldatei bearbeiten, bleibt die Installations-Layer daher gecacht, und der Build wird beim Kopierschritt fortgesetzt. Vertauschen Sie diese beiden Zeilen, installiert eine Änderung an einem einzigen Zeichen alle Abhängigkeiten neu. COPY . . macht die Layer ungültig, auf der npm ci aufbaut. Dieselbe Struktur gilt für pip install -r requirements.txt und go mod download.

--no-cache ist das richtige Werkzeug, wenn Sie vermuten, dass eine veraltete Layer Ihre Korrektur verdeckt. Als Standard ist es ungeeignet, weil dadurch die Wiederverwendung verworfen wird, die durch die Reihenfolge im Dockerfile erreicht werden soll.

Ein Eintrag wird durch das Image festgelegt und kann durch Compose überschrieben werden: CMD im Dockerfile ist der Befehl, den das Image standardmäßig ausführt. Ein command:-Schlüssel im Dienst ersetzt ihn. Wie Command und Entrypoint zusammenspielen ist hier relevant, weil eine Compose-Überschreibung ein frisch erstelltes Image genauso wie das alte verhalten lassen kann.

Build-Kontext und .dockerignore

context: . verpackt dieses Verzeichnis und sendet es an den Builder, bevor die erste Anweisung ausgeführt wird. Alles darin wird übertragen, einschließlich .git und jedes Datenverzeichnisses, das Sie neben dem Quellcode ablegen. Wenn ein Build beim Schritt zur Übertragung des Kontexts pausiert, obwohl das Projekt ansonsten unverändert ist, ist der Kontext zu groß.

Eine .dockerignore-Datei im Stammverzeichnis des Kontexts schließt Pfade von dieser Übertragung aus. Die Syntax ähnelt .gitignore.

.git
node_modules
*.log
data/
.env

Das hat zwei Vorteile. Die Übertragung wird kleiner, sodass jeder Build schneller startet. Außerdem kann COPY . . .env nicht mehr in das Image kopieren. Jeder, der dieses Image abruft, könnte die Datei sonst wieder auslesen.

Ein Fall, bei dem der Build mit der Zeit langsamer wird, ist ein Bind-Mount. Ein benanntes Volume liegt außerhalb Ihres Projektverzeichnisses. Ein Bind-Mount wie ./data:/var/lib/postgresql/data liegt dagegen innerhalb des Build-Kontexts. Dadurch werden Ihre Builds mit jeder Woche langsamer, während die Datenbank wächst. Eine Zeile in .dockerignore behebt das Problem. Bind-Mounts im Vergleich zu benannten Volumes behandelt den weitergehenden Abwägungsprozess.

Build-Argumente bergen ein kleineres Risiko derselben Art. Werte, die über args: übergeben werden, sind in der Image-Historie für jeden sichtbar, der das Image besitzt. Verwenden Sie dort daher eine Versionsnummer und niemals ein Token. Env-Dateien und Secrets in Compose erklärt stattdessen, wo Zugangsdaten hingehören.

Sollten Sie auf dem VPS bauen oder an anderer Stelle bauen und das Image dann abrufen?

Das Bauen auf dem Server, der Ihren Netzwerkverkehr bedient, ist die Standardlösung, weil dies der kürzeste Weg ist: git pull, dann docker compose up -d --build. Das ist auf einem kleinen Server in Ordnung, von dem noch niemand abhängig ist. Aus zwei messbaren Gründen und einem weiteren Grund, der sich erst an einem schlechten Tag zeigt, ist es später nicht mehr sinnvoll.

Arbeitsspeicher. Beim Build laufen Compiler und Bundler neben Ihrer aktiven Anwendung. Diese Prozesse benötigen in den meisten Stacks besonders viel Arbeitsspeicher. Auf einem VPS mit 1 GB ist ein JavaScript-Bundler oder ein Rust-Compile-Vorgang regelmäßig der größte Prozess auf dem Server. Wenn dem Kernel der Arbeitsspeicher ausgeht, beendet er den größten Prozess: Entweder bricht der Build mit Killed und Exit-Status 137 ab, oder stattdessen wird Ihre Datenbank beendet und die Site fällt während eines Deployments aus. dmesg -T | grep -i oom gibt die Kill-Zeile mit dem Prozessnamen aus. Damit können Sie feststellen, welcher der beiden Fälle eingetreten ist, statt zu raten.

Festplatte. Jeder Build hinterlässt Layer, und der Builder verwaltet zusätzlich einen eigenen Cache. docker system df zeigt beides an. Die Zeile für den Build-Cache wächst dabei weiter. Geben Sie mit docker image prune nicht mehr referenzierte Images und mit docker builder prune gecachte Layer frei. Eine volle Festplatte verhindert mehr als nur den Build. Auch die Datenbank kann dann nicht mehr schreiben. Dieser Fehler verursacht deutlich größere Schäden als ein langsames Deployment.

Reproduzierbarkeit. Ein auf dem Server erstelltes Image existiert nur auf diesem Server. Für ein Rollback müssen Sie den alten Commit auschecken und den Build erneut ausführen. Es ist nicht garantiert, dass dieser Build dasselbe Ergebnis erzeugt, weil sich der Base-Tag geändert haben kann und die Paket-Mirrors ebenfalls aktualisiert wurden. Wenn Sie an anderer Stelle bauen und einen Tag pushen, wird ein Rollback zu einer Änderung: Setzen Sie image: auf den vorherigen Tag und führen Sie docker compose up -d aus.

Eine belastbare Lösung ist einfach. Ihre Continuous Integration führt den Build aus und pusht registry.example.com/acme/web:<git-sha>. Die Compose-Datei auf dem VPS enthält image:, jedoch überhaupt keinen Schlüssel build:. Das Deployment besteht dann aus zwei Befehlen und benötigt nahezu keinen Arbeitsspeicher.

docker compose pull
docker compose up -d

Führen Sie docker login registry.example.com einmal auf dem Server aus. Danach kann Compose private Tags abrufen.

Behalten Sie den Build-Abschnitt für die Entwicklung bei, statt ihn zu löschen. Legen Sie ihn in einer Datei ab, deren Namen Sie selbst festlegen.

# compose.dev.yaml
services:
  web:
    build:
      context: .
    pull_policy: build
docker compose -f compose.yaml -f compose.dev.yaml up -d --build

Nennen Sie diese Datei compose.dev.yaml und nicht compose.override.yaml. Compose lädt eine Override-Datei automatisch, sobald sie vorhanden ist. Eine versehentlich auf den Server kopierte Override-Datei würde daher unbemerkt wieder Builds auf dem Server aktivieren. Mehrere Compose-Dateien schichten erklärt, wie die Zusammenführung die einzelnen Schlüssel auflöst.

Die Architektur-Falle beim Bauen auf einem anderen System

Ein Image enthält die CPU-Architektur, für die es gebaut wurde. Wenn Sie auf einem Apple-Silicon-Laptop bauen, das Image pushen und den Tag anschließend auf einen x86_64-VPS pullen, warnt Docker, dass die angeforderte Image-Plattform nicht zur erkannten Host-Plattform passt. Der Prozess beendet sich dann mit exec format error. Das sieht wie eine beschädigte Binärdatei aus, ist aber keine. Bauen Sie das Image explizit für das Zielsystem:

docker buildx build --platform linux/amd64 \
  -t registry.example.com/acme/web:1.4.2 --push .

Das gleiche Problem tritt umgekehrt auf, wenn Ihr Laptop x86 verwendet und Sie einen ARM-VPS statt eines x86-VPS betreiben. Wenn CI auf der Architektur baut, auf der Sie deployen, stellt sich diese Frage nicht.

Was Sie nach einem Deploy prüfen sollten

  • docker compose images gibt das Image und den Tag für jeden laufenden Container aus. Eine geänderte Image-ID bestätigt, dass der neue Build aktiv ist.
  • docker compose config gibt die zusammengeführte Datei nach der Variablensubstitution aus. So können Sie den endgültigen Imagenamen lesen, den Compose verwendet, bevor Sie etwas ausführen.
  • docker compose logs -f web für die erste halbe Minute nach dem Austausch. Ein Container, der startet und beendet wird, startet in einer Schleife neu, anstatt dauerhaft zu laufen. Diese Schleife bleibt unbemerkt, wenn Sie sie nicht prüfen.
  • docker image ls zeigt eine Spalte CREATED an. Ein Image, das älter als Ihr letzter Commit ist, wurde nicht neu erstellt.

Wenn Sie die Datei, auf die diese Prüfungen angewendet werden, noch zusammenstellen, behandelt die Grundlagen einer Compose-Datei auf einem VPS die zugehörigen Schlüssel. das Compose-Befehls-Spickblatt führt die übrigen Subcommands auf.

FAQ

Kann ich build und image im selben Service verwenden?

Ja. Das ist die übliche Konfiguration für ein Projekt, das Sie selbst bauen. Compose baut aus dem Abschnitt build: und versieht das Ergebnis mit dem Wert von image: als Tag. Dieses Tag verwendet docker compose push, um das Image an eine Registry zu übertragen, und ein anderer Rechner verwendet es zum Abruf. Ohne einen Schlüssel image: baut Compose weiterhin, benennt das Image jedoch nach dem Projekt und dem Service. Außerdem weist Compose darauf hin, dass das fehlende Attribut verhindert, dass das Image übertragen wird.

Warum übernimmt docker compose up meine Änderung an der Dockerfile nicht?

Weil up nur prüft, ob ein Image mit diesem Namen vorhanden ist. Wenn ein solches Image vorhanden ist, startet Compose dieses Image und vergleicht es nicht mit Ihrer Dockerfile oder Ihren Quelldateien. Führen Sie docker compose up -d --build aus. Alternativ können Sie docker compose build web gefolgt von docker compose up --no-deps -d web ausführen, um einen einzelnen Service zu ersetzen. Wenn Sie pull_policy: build für den Service setzen, wird bei jedem up ein Rebuild durchgeführt. Das eignet sich für einen Entwicklungsrechner.

Was ist der Unterschied zwischen --build und --force-recreate?

--build baut das Image erneut und erstellt anschließend die Container neu, deren Image geändert wurde. --force-recreate erstellt Container aus dem bereits vorhandenen Image neu. Daher kann dieser Befehl niemals eine Codeänderung übernehmen. Wenn Ihre Änderung in den Quelldateien oder in der Dockerfile liegt, ist --build das gewünschte Flag. --force-recreate dient dazu, den Container selbst zurückzusetzen, beispielsweise um seine beschreibbare Schicht zu löschen und dabei dasselbe Image beizubehalten.

Sollte ich meine Docker-Images auf dem VPS oder an einem anderen Ort bauen?

Bauen Sie die Images an einem anderen Ort und rufen Sie ein Tag ab, sobald der Rechner auch Netzwerkverkehr verarbeitet. Der Build konkurriert mit Ihrer Anwendung um Arbeitsspeicher. Auf einem kleinen VPS löst der Kernel diesen Konflikt möglicherweise, indem er den größten Prozess beendet. Das kann der Build oder Ihre Datenbank sein. Builds hinterlassen außerdem Cache auf der Festplatte, den niemand automatisch für Sie entfernt. Für ein kleines Projekt ohne Benutzer ist das Bauen auf dem Server weiterhin unproblematisch. Ein späterer Wechsel ist einfach, wenn Sie den Abschnitt build: in einer Compose-Datei nur für die Entwicklung belassen.

Wie verhindere ich, dass der Docker-Build-Cache meine Festplatte füllt?

Führen Sie docker system df aus, um zu sehen, wie viel Speicher Ihre Images und Ihr Build-Cache jeweils belegen. docker builder prune entfernt gecachte Layer. docker image prune entfernt die verwaisten Images, die frühere Builds hinterlassen haben. Wenn Sie -a hinzufügen, arbeitet der Befehl aggressiver. Der nächste Build muss dann ohne vorhandenen Cache beginnen. Planen Sie docker system prune -af --volumes nicht auf einem Server ein, weil --volumes jedes Volume löscht, das derzeit von keinem Container verwendet wird. Ein Stack, den Sie für Wartungsarbeiten angehalten haben, speichert Ihre Datenbank genau in einem solchen Volume.