Docker für Strategien-Deployment.
Wer eine Trading-Strategie schreibt, sollte sie auch in fünf Jahren noch starten können — ohne dass eine kaputte Python-Version, ein verschwundenes Paket oder ein OS-Update zum Stolperstein wird. Docker ist die einfachste, langweiligste und beste Antwort auf dieses Problem. In diesem Artikel zeige ich, wie ich Strategien-Images baue, welche Patterns sich bewähren und welche Anti-Patterns ich bei jedem zweiten Mandat sehe.
Warum überhaupt Docker für Trading?
Eine Strategie hat selten weniger als zehn Python-Abhängigkeiten. pandas,
numpy, ein Broker-SDK, vielleicht torch oder
xgboost, dazu eigene Utility-Pakete. Jedes Paket hat seine eigene
Versionspolitik. Was heute auf Ihrem Laptop funktioniert, funktioniert in zwei Jahren
mit einem frischen Python-Setup vielleicht nicht mehr — weil pandas eine
Funktion entfernt oder ein Broker-SDK eine Breaking-Change-Version released hat.
Docker zementiert den Zustand. Ein gebautes Image enthält die exakten Versionen aller Dependencies, die exakte Python-Runtime, die exakten System-Bibliotheken. Wenn das Image heute läuft, läuft es auch in fünf Jahren — solange Sie den Image-Speicher erhalten. Das ist nicht nur Komfort, sondern Compliance: bei Mandanten-Projekten ist Reproduzierbarkeit oft eine vertragliche Anforderung.
Das richtige Base-Image.
Hier passieren die ersten Fehler. FROM python:3.12 klingt unschuldig,
zieht aber ein 1 GB grosses Debian-Image mit hunderten Paketen, die Sie nicht brauchen
und die alle CVEs haben können. Die Alternativen, sortiert nach Größe:
python:3.12-slim: 150 MB, Debian-basiert, gute Balance. Mein Default für die meisten Strategien.python:3.12-alpine: 50 MB, aber Alpine nutzt musl statt glibc.numpy,pandasund Co. müssen kompiliert werden — Build-Zeit explodiert, ML-Pakete können Probleme machen. Nicht empfohlen für Strategien mit ML-Stack.gcr.io/distroless/python3-debian12: 50 MB, keine Shell, keine Tools, nur Python. Beste Security, aber Debugging im Container ist nahezu unmöglich. Für extra-kritische Produktions-Workloads.
Für die Hauptmasse der Trading-Workloads ist python:3.12-slim die richtige
Wahl. Sicher genug, klein genug, und es funktioniert mit allen relevanten Python-Paketen
ohne Verrenkungen.
Multi-Stage-Build.
Das wichtigste Muster für saubere Trading-Images: Multi-Stage-Builds. Ein Build-Stage installiert Dependencies und kompiliert was kompiliert werden muss, ein Runtime-Stage kopiert nur die fertigen Artefakte. Resultat: kleinere Images, weniger Angriffsfläche, schnellere Pulls.
# Dockerfile fuer eine Trading-Strategie
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim AS build
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
RUN apt-get update && apt-get install --no-install-recommends -y \
build-essential \
git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install --upgrade pip poetry==1.8.3 \
&& poetry config virtualenvs.in-project true \
&& poetry install --only=main --no-root --no-interaction
COPY src ./src
RUN poetry install --only=main --no-interaction
# --- Runtime-Stage ---
FROM python:3.12-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/app/.venv/bin:${PATH}"
RUN apt-get update && apt-get install --no-install-recommends -y \
ca-certificates \
tzdata \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --gid 10001 trader \
&& useradd --uid 10001 --gid trader --shell /usr/sbin/nologin trader
WORKDIR /app
COPY --from=build --chown=trader:trader /app/.venv /app/.venv
COPY --from=build --chown=trader:trader /app/src /app/src
USER trader
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD python -m strategy.healthcheck || exit 1
ENTRYPOINT ["python", "-m", "strategy.run"]
Sechs Punkte, die in diesem Dockerfile wichtig sind. Erstens syntax=docker/dockerfile:1.7
aktiviert moderne BuildKit-Features. Zweitens deterministisch fixierte Versionen
(poetry==1.8.3). Drittens kein Pip-Cache im Image. Viertens ein non-root-User
mit fixer UID — wichtig für Kubernetes-Pod-Security. Fünftens ein eingebauter
Healthcheck. Sechstens ENTRYPOINT statt CMD — der Container
tut genau eine Sache.
Reproduzierbare Builds.
Ein typisches Problem: das Image, das letzte Woche gebaut wurde, lässt sich heute nicht reproduzieren — weil ein transitives Dependency-Paket inzwischen eine neue Patch-Version released hat. Drei Maßnahmen verhindern das:
- Lockfile:
poetry.lockoderrequirements.txtmit Hashes. Damit ist jede Abhängigkeitsversion fixiert. - Base-Image-Pinning per SHA-256: statt
python:3.12-slimschreiben Siepython:3.12-slim@sha256:abc123.... Damit ist auch das Base-Image deterministisch. - Build-Argumente für Git-Hash: jedes gebaute Image bekommt als Label den Git-SHA, aus dem es gebaut wurde. So sehen Sie im Live-Cluster sofort, welcher Code-Stand läuft.
# In Dockerfile
ARG GIT_SHA=unknown
ARG BUILD_TIME=unknown
LABEL org.opencontainers.image.revision="${GIT_SHA}" \
org.opencontainers.image.created="${BUILD_TIME}" \
org.opencontainers.image.source="https://github.com/mg-quant/strategies"
# Beim Build
docker build \
--build-arg GIT_SHA=$(git rev-parse --short HEAD) \
--build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t ghcr.io/mg-quant/momentum-v3:$(git rev-parse --short HEAD) \
-t ghcr.io/mg-quant/momentum-v3:latest \
.
docker-compose für lokale Entwicklung.
Bevor eine Strategie in Kubernetes geht, läuft sie meist erst Wochen lokal. Dafür ist
docker-compose immer noch das ehrlichste Werkzeug: einfach genug für
Solo-Entwicklung, mächtig genug für realistische Setups mit Datenbank, Broker-Mock
und Monitoring.
# docker-compose.yml
version: "3.9"
services:
strategy:
build:
context: .
dockerfile: Dockerfile
image: momentum-v3:dev
env_file:
- .env.dev
depends_on:
postgres:
condition: service_healthy
broker-mock:
condition: service_started
volumes:
- ./src:/app/src:ro
ports:
- "8080:8080"
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: dev
POSTGRES_DB: trading
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
volumes:
- postgres_data:/var/lib/postgresql/data
broker-mock:
image: ghcr.io/mg-quant/ibkr-mock:1.2.0
ports:
- "7497:7497"
prometheus:
image: prom/prometheus:v2.54.1
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
ports:
- "9090:9090"
volumes:
postgres_data:
Wichtig: in der Dev-Umgebung werden Source-Files als Bind-Mount eingebunden
(./src:/app/src:ro). Das ermöglicht Hot-Reload ohne ständigen Rebuild,
der ro-Flag verhindert, dass der Container Files verändert. In Produktion
ist das Image immutable.
Security-Patterns.
Trading-Container haben Zugriff auf Geld. Drei Sicherheitsmaßnahmen, die ich für jeden produktiven Container durchsetze:
- Non-root-User: jeder Container läuft als unprivilegierter User. Niemals als root, auch nicht „nur kurz zum Debuggen".
- Read-only-Rootfilesystem: im Pod-Spec wird das Root-FS readonly gemounted, nur explizit definierte Volumes sind schreibbar. Damit kann ein kompromittierter Container nicht beliebig Dateien ablegen.
- Image-Scanning: Trivy oder Grype scannt jedes Build-Image vor dem Push in die Registry. Bei Critical-CVEs in produktiven Pfaden bricht der Build ab.
# Im GitHub-Actions-Workflow
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Scan image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: "app:${{ github.sha }}"
format: "table"
exit-code: "1"
severity: "CRITICAL,HIGH"
ignore-unfixed: true
Image-Registry und Retention.
Ein praktisches Detail, das oft vergessen wird: Image-Registries kosten Speicher.
Wenn Sie jeden Commit als Image taggen, haben Sie nach einem Jahr mehrere hundert
Images im Registry. Setzen Sie Retention-Policies: alle Images älter als 90 Tage
werden gelöscht, ausgenommen Tags wie release-*, v*.*.*
oder latest. Bei GitHub Container Registry funktioniert das mit einer
Action wie actions/delete-package-versions.
Wann Docker zu viel ist.
Auch wenn ich Docker fast immer empfehle — es gibt Fälle, wo es Overhead ohne Nutzen ist. Ein einmaliges Recherche-Skript, das einmal pro Quartal in JupyterLab läuft? Kein Docker nötig. Ein winziger Bash-Cron, der einmal pro Tag eine E-Mail schickt? Auch nicht. Docker lohnt sich, sobald Code mehrfach deployed wird, mehrere Maschinen sieht, oder eine produktive Rolle spielt. Dann zahlt sich die initiale Disziplin aus.
Sie wollen Ihre Trading-Strategien sauber containerisieren oder einen reproduzierbaren Build-Prozess aufsetzen? Erstgespräch buchen — wir gehen Dockerfile, Pipeline und Registry-Strategie gemeinsam an.