Neu veröffentlicht: E-Commerce mit Power Pages, Stripe & Analytics

· DevOps  · 5 minuten Lesezeit

Das Problem, das ich nicht akzeptiert habe: Wie ein docker exec zu einem automatisierten Setup wurde

Nach dem ersten erfolgreichen Start meines RAG-Systems dachte ich: fertig. Dann habe ich einen docker exec-Schritt im Quick Start gesehen und nicht schlafen können. Hier ist wie ein kleines Reibungsmoment zu einer besseren Architektur geführt hat.

Inhalt

Der Quick Start, der mich gestört hat

Das System lief. Docker Compose hochgefahren. Backend verbunden. Chrome Extension schickt Metadaten, Qdrant speichert Embeddings. Erster Erfolg.

Dann habe ich die README geschrieben und bin über diese Zeilen gestolpert:

# Nach dem ersten Start: Modelle manuell pullen
docker exec ollama_local ollama pull nomic-embed-text
docker exec ollama_local ollama pull llama3.2

Ich hab die Zeilen getippt. Ich hab sie committed. Und dann hab ich aufgehört zu tippen.

Das ist der Moment, wo die meisten Entwickler weitermachen. Ich nicht.

Nicht weil ich pedantisch bin. Sondern weil ich dieses Projekt irgendwann der Öffentlichkeit übergeben will. Und weil jede zusätzliche Zeile im Quick Start ein Mensch ist, der das Projekt wieder schließt.

Was ist eigentlich das Problem?

Auf der Oberfläche: Zwei extra Befehle nach dem Start.

Darunter: Ein fundamentaler Bruch im mentalen Modell.

docker-compose up heißt: Das System ist bereit. Fertig. Jetzt funktioniert es.

Wenn danach noch manuelle Schritte notwendig sind — Models pullen, Services neu starten, Config anpassen — dann lügt der erste Befehl. Das System ist nicht bereit. Es ist halbfertig gestartet und wartet darauf, dass der Entwickler den Rest erledigt.

Das ist technische Schulden. Nicht in Form von schlechtem Code, sondern in Form von implizitem Wissen, das nirgendwo dokumentiert ist außer im Quick Start den niemand vollständig liest.

Der Ansatz: Entrypoint als Initialisierungsort

Die Lösung war eigentlich offensichtlich, nachdem ich aufgehört hatte, das Problem falsch zu denken.

Docker-Container haben Entrypoints. Entrypoints laufen beim Start. Wenn Initialisierungslogik beim Start laufen soll — warum nicht dort?

Ich habe ein eigenes ollama/Dockerfile erstellt:

FROM ollama/ollama:latest

RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

Das offizielle ollama/ollama:latest-Image hat kein curl. Ohne curl kann man nicht auf die API warten. Also wird curl installiert. Drei Zeilen.

Das Herzstück: entrypoint.sh

#!/bin/bash
set -e

MODELS=("nomic-embed-text" "llama3.2")

# Server im Hintergrund starten
ollama serve &
OLLAMA_PID=$!

# Auf API warten
echo "Waiting for Ollama API..."
until curl -sf http://localhost:11434/api/tags > /dev/null 2>&1; do
  sleep 1
done
echo "Ollama API ready."

# Modelle idempotent pullen
for model in "${MODELS[@]}"; do
  if ollama show "$model" > /dev/null 2>&1; then
    echo "Model '$model' already present. Skipping."
  else
    echo "Pulling model '$model'..."
    ollama pull "$model"
    echo "Model '$model' pulled successfully."
  fi
done

# Hauptprozess am Leben halten
wait $OLLAMA_PID

Das Script ist in drei Teile gegliedert. Jeder Teil löst ein spezifisches Problem.

Teil 1: Server im Hintergrund starten. ollama serve & — das & ist entscheidend. Ohne es blockiert der Prozess und das Script kommt nie zu den Modellen. Aber den PID speichern: OLLAMA_PID=$!. Der brauchen wir am Ende.

Teil 2: Auf die API warten. Ollama braucht eine Sekunde zum Hochfahren. Wenn man sofort ollama pull ausführt, ist die API noch nicht ready und der Befehl schlägt fehl. Die until-Schleife fragt jede Sekunde nach, ob /api/tags antwortet. Erst dann geht’s weiter.

Teil 3: Modelle idempotent prüfen. ollama show "$model" gibt Exit-Code 0 zurück wenn das Modell vorhanden ist, nicht-null wenn nicht. Kein pull wenn nicht nötig. Beim zweiten Start — wenn die Modelle im Volume liegen — überspringt der Loop alles. Start in Sekunden, nicht Minuten.

Und dann: wait $OLLAMA_PID. Das ist wichtig. Wenn das Script hier endet, endet der Container. wait hält den Container am Leben, solange ollama serve läuft. Und es leitet Signale korrekt weiter — docker stop funktioniert sauber.

Idempotenz: Der unterschätzte Aspekt

Das Wort klingt akademisch. Was es bedeutet ist simpel: Der gleiche Befehl mehrmals ausführen hat dieselbe Wirkung wie einmal.

Beim ersten Start: Modelle nicht vorhanden → Download. Beim zweiten Start: Modelle vorhanden → Skip. Nach docker-compose down ohne -v: Modelle im Volume → Skip. Nach docker-compose down -v: Volume gelöscht → Download.

Das ist das erwartete Verhalten. Kein “oh ich hab vergessen zu pullen”. Kein “warum lädt das nochmal herunter”. Das System weiß was es hat und reagiert darauf.

Das Ergebnis in der docker-compose.override.yml

Statt:

ollama:
  image: ollama/ollama:latest

Jetzt:

ollama:
  build: ./ollama

Eine Zeile Änderung. Ein komplett anderes Verhalten beim Start.

Das ist die Schönheit dieses Ansatzes: Die Komplexität steckt im Container, nicht in der Dokumentation.

Was dieser Moment über Developer Experience aussagt

Es gibt einen Begriff in der Produktentwicklung: Time to Value. Die Zeit vom ersten Kontakt bis zum ersten “Aha, das funktioniert!”-Moment.

Bei Developer Tools ist das Time to First docker-compose up.

Jede Zeile in einem Quick Start, die nach up kommt, verlängert die Time to Value. Jeder manuelle Schritt ist eine Hürde. Nicht weil Entwickler zu faul sind — sondern weil Hürden akkumulieren. Zwei extra Befehle plus ein Neustart plus eine unklare Fehlermeldung macht aus einem fünfminütigen Setup ein fünfzehnminütiges Rätsel.

Ich baue dieses System für mich. Aber ich baue es als ob ich es für andere baue. Weil “ich” in drei Monaten auch jemand ist, der nicht mehr weiß was er sich gedacht hat.

Das Prinzip dahinter

Mache das Richtige einfach und das Falsche schwer.

In diesem Fall: Das Richtige ist, dass das System beim ersten docker-compose up --build vollständig läuft. Das Falsche ist, nach dem Start noch manuell eingreifen zu müssen.

Indem ich die Initialisierungslogik in den Container verlagere, mache ich das Richtige automatisch. Kein Entwickler kann vergessen, die Modelle zu pullen — weil es keinen Schritt zum Vergessen gibt.

Das ist keine Über-Ingenieurskunst. Das ist Respekt für die Zeit anderer Menschen.

Was als nächstes kommt

Das System hat jetzt einen vollständig automatisierten ersten Start. Ein Befehl, alles läuft.

Der nächste Schritt: Dieselbe Sorgfalt auf den Produktivbetrieb anwenden. Azure Container Apps, Monitoring, automatische Skalierung.

Aber das ist eine andere Geschichte.


Das ist Teil einer laufenden Dokumentation meines Local Insight RAG Ecosystems — einem AI-gestützten Memory-System für meine Arbeit als Power Pages & React Freelancer. Wenn dich die technischen Details interessieren, lies auch:

Zurück zum Blog

Ähnliche Beiträge

Alle Beiträge ansehen