← Alle Insights

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:

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:

# 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:

# 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.