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

· DevOps  · 6 minuten Lesezeit

Warum ein manueller docker exec zum automatisierten Ollama Entrypoint-Setup führte

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.

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 von Copilot schreiben lassen und bin über diese Zeilen gestolpert:

# after first start: pull models manually
docker exec ollama_local ollama pull nomic-embed-text
docker exec ollama_local ollama pull llama3.2

Copilot hat die Zeilen geschrieben. Ich hab sie committed.

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 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 ist kein Warten auf die API möglich. Also wird curl installiert. Drei Zeilen.

Das entrypoint.sh Script

#!/bin/bash
set -e

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

# start server in background
ollama serve &
OLLAMA_PID=$!

# wait for API
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."

# idempotently pull models
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

# keep main process alive
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. Den PID speichern: OLLAMA_PID=$!. Der wird am Ende gebraucht.

Teil 2: Auf die API warten. Ollama braucht eine Sekunde zum Hochfahren. Wird sofort ollama pull ausgefü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 es 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. 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 Begriff klingt sperrig. Was er 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.

Die Komplexität steckt im Container, nicht in der Dokumentation.

Ablaufdiagramm des Ollama Entrypoint-Scripts mit den drei Phasen: Server starten, API warten, Modelle prüfen Abbildung: Das Script läuft beim Container-Start automatisch durch: Server hochfahren, auf die API warten, Modelle idempotent prüfen und bei Bedarf herunterladen.

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 dreißigminü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, wird 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.

Alle Artikel der Serie

  1. Vision und Systemübersicht: Chrome Extension, RAG-Architektur, Projekthintergrund: Artikel lesen

  2. RAG-System Aufbau: Qdrant, Embeddings, Cosine-Ähnlichkeit in TypeScript: Artikel lesen

  3. AI Provider Abstraktion: Ollama vs. OpenAI, Interface-Design, kein Vendor-Lock-in: Artikel lesen

  4. Chrome Extension MV3: Drei isolierte Laufzeitkontexte, Message Passing, Strategy Pattern: Artikel lesen

  5. Docker Compose Strategie: Override-Pattern, von lokal zu Azure: Artikel lesen

  6. Ollama lokal vs. Docker: Die Entscheidung und ihre Konsequenzen: Artikel lesen

  7. Ollama Auto-Pull Entrypoint: Automatisiertes Modell-Setup beim Container-Start (dieser Artikel)

  8. tsconfig und Vite: Node16 vs. bundler, warum Vite eigene Regeln hat: Artikel lesen

  9. Instagram Caption mit MutationObserver vollständig laden: Artikel lesen

  10. Chrome Extension Foundation mit Health-Dot und Retry-Queue: Artikel lesen

  11. Phase 2 Features: Shadow DOM Overlay, Tailwind v4, Duplicate Detection: Artikel lesen

  12. Race Condition bei der Plattformerkennung: Wie ein UI-Event die Instagram-Erkennung bricht: Artikel lesen

  13. PostId-Extraktion in zwei Instagram-Layouts: querySelector vs. Ancestor-Traversal: Artikel lesen

  14. Instagram Karussell vollständig erfassen mit MutationObserver: Lazy-Loading, Observer-before-click, Timeout-Fallback: Artikel lesen

  15. Notiz und Tags beim Screenshot-Speichern: Artikel lesen

  16. Instagram Tastatur-Shortcuts blockieren Chrome Extension Eingaben: Artikel lesen

  17. Lowercase-Normalisierung und Duplikat-Erkennung im Tag-Input: Artikel lesen

  18. Zitadel Login V2 in Docker Compose: drei versteckte Fehler: Artikel lesen

  19. PKCE OAuth in einer Chrome MV3 Extension: Artikel lesen

  20. React Frontend mit react-oidc-context und Zitadel: Artikel lesen

  21. Vite Build-Time-Umgebungsvariablen in Docker: Artikel lesen

  22. Event-Driven Ingestion mit BullMQ und Redis: Artikel lesen

  23. MinIO statt Azurite: S3-kompatible Objektspeicherung lokal und auf Hetzner: Artikel lesen

  24. access_token, id_token und der Userinfo-Endpoint: was wohin gehört: Artikel lesen

  25. Qdrant Multi-Tenancy: Pro Nutzer eine eigene Collection: Artikel lesen

  26. Wenn Backend und Frontend unterschiedliche Typen kennen: Artikel lesen

  27. Zitadel Bootstrap entfernt: Host-Header-Bug und manuelles Setup: Artikel lesen

  28. Backend Code Review: sechs Probleme vor dem Launch behoben: Artikel lesen

  29. Traefik statt NGINX: Reverse Proxy für einen wachsenden Docker-Compose-Stack: Artikel lesen

  30. Zweischichtiges Rate Limiting: Traefik und express-rate-limit mit Redis: Artikel lesen

  31. DSGVO Art. 17 korrekt implementieren: Promise.allSettled und Export-Batching: Artikel lesen

  32. Embedding-Modell-Lock-in: Warum mxbai-embed-large eine Produktionsentscheidung für immer ist: Artikel lesen

  33. Docker Volumes in Produktion: Named Volumes, Bind Mounts und der Hetzner-Volume-Trick: Artikel lesen

  34. Zwei Sicherheitslücken vor dem Launch: Redis ohne Auth und ein offener Qdrant-Admin-Port: Artikel lesen

  35. Traefik als einziger Einstiegspunkt im Docker Compose Stack: Artikel lesen

  36. Zitadel hinter Traefik richtig verdrahten mit Issuer, JWKS und Login V2: Artikel lesen

  37. Frontend gesund machen wenn der nginx Healthcheck an localhost scheitert: Artikel lesen


Du baust ein System mit lokaler KI-Inferenz und willst den Setup-Aufwand für neue Entwickler minimieren? Lass uns das gemeinsam einschätzen.

Zurück zum Blog

Ähnliche Beiträge

Alle Beiträge ansehen