Michael Silzle
Michael ist Softwareentwickler und Geschäftsführer von codeunity
02.12.2025 | 3 min Lesezeit
Ich habe in einem Projekt kürzlich aufgrund einer fehlerhaften Konfiguration ein nicht freigegebenes Docker Image in die produktive Umgebung deployed.
Die .NET-Anwendung führte beim Start automatisch Entity Framework Migrationen aus und veränderte damit unbemerkt das Datenbankschema.
Als ich feststellte, dass die Umgebung nicht mehr funktionsfähig war, spielte ich das vorherige Image wieder ein.
Der Container startete problemlos, was den Eindruck erweckte, das Problem sei behoben.
Erst beim Deployment des finalen Release Images wurden die Folgen sichtbar: Das Backend startete nicht mehr.
Nach langwieriger Fehlersuche und dem Genuss einiger Tassen unserer <coderbohne/> konnte ich die Ursache finden: die Datenbank.
Weil mir die automatische Datenbankmigration nicht bewusst war, habe ich den Fehler zunächst nicht in diesem Bereich vermutet.
So wurde mir klar, dass automatische Migrationen im Anwendungscode bei containerisierten Deployments erhebliche Risiken bergen und eigentlich ein „No-Go“ sind.
Das automatische Ausführen von Migrationen in der Program.cs wird häufig genutzt, da die Datenbank so stets aktuell ist und man sich nicht manuell darum kümmern muss.
In der Praxis führt dies jedoch häufig zu Problemen. Container sind stateless, lassen sich beliebig skalieren und werden je nach Orchestrierungssystem unter Umständen mehrfach oder parallel gestartet.
Wenn jede dieser Instanzen eigenständig Migrationen ausführt, entsteht ein unkontrollierbarer Wettlauf beim Migrieren des Datenbankschemas.
Außerdem ist unklar, wann genau eine Migration ausgeführt wurde und durch welche Image-Version diese ausgelöst wurde.
Der JetBrains-Artikel „Database Migrations in the Real World“ beschreibt dieses Problem treffend:
Migrationen dürfen nicht im Startprozess der Anwendung versteckt sein. Sie sind Teil des Deployments und nicht des Container-Lifecycles. Werden Migrationen unkontrolliert beim Start ausgeführt, können ein fehlerhaftes Image oder eine falsch konfigurierte Umgebung unmittelbar strukturelle Änderungen in der Produktivdatenbank verursachen. Mit steigender Komplexität der Systeme nimmt dieses Risiko weiter zu.
Als zuverlässige Alternative empfiehlt es sich, Migrationen bewusst in die CI/CD-Pipeline zu integrieren. Mithilfe einer GitHub Action oder eines vergleichbaren Deployment Steps kann die Datenbank gezielt aktualisiert werden, bevor das eigentliche Anwendungsimage ausgerollt wird. Dieser Ansatz schafft Transparenz, Wiederholbarkeit und eine klare Trennung der Verantwortlichkeiten. Wenn Migrationen ausschließlich im Deployment-Prozess ausgeführt werden, ist jederzeit nachvollziehbar, welche Änderungen unter welchen Bedingungen erfolgt sind. Gleichzeitig entfällt das Risiko, dass ein einzelner Containerstart unbeabsichtigt strukturelle Änderungen an produktiven Daten vornimmt.
Neben dem CI/CD-Ansatz gibt es auch weitere bewährte Vorgehensweisen. In containerisierten Systemen kann beispielsweise ein dedizierter Migrations-Container oder ein Job, der vor dem Hochfahren der Anwendung ausgeführt wird, eingesetzt werden. Dadurch wird sichergestellt, dass Migrationen einmalig und in einem definierten Kontext ausgeführt werden – unabhängig von der Anzahl der später gestarteten Instanzen. In Umgebungen, die nicht containerisiert sind, beispielsweise bei On-Premise-Installationen, kann das automatische Migrieren im Anwendungscode nach wie vor sinnvoll sein. Dabei besteht nämlich nicht das Risiko, dass parallele Instanzen oder fehlerhafte Images unerwünschte Migrationen auslösen. Wenn eine Anwendung in einer neuen Version bereitgestellt wird, ist es in der Regel sogar erwünscht, dass die Anwendung selbst dafür sorgt, dass die Datenbank explizit auf den ausgelieferten Stand migriert wird. Dennoch ist es wichtig, Migrationen sauber zu versionieren und sie vorab in einer isolierten Umgebung zu testen.
Der eingangs beschriebene Vorfall macht deutlich, wie wichtig kontrollierte Datenbankmigrationen sind. Migrationen gehören in den Release-Prozess und nicht in den Startvorgang der Anwendung. Am zuverlässigsten lässt sich dies über einen CI/CD-Pipeline-Job oder einen dedizierten Migrationsjob erreichen. Eine saubere Trennung von Anwendungs- und Migrationslogik sorgt dafür, dass Deployments wesentlich verlässlicher und entspannter ablaufen.

