Docker · Datenbanken · MySQL · PostgreSQL · DevOps
Containerisierte Datenbanken
richtig initialisieren

Containerisierte Datenbanken verhalten sich anders als klassische Datenbankinstallationen: Init-Verzeichnisse, Volume-Persistenz und Health-Check-Synchronisation sind Konzepte, die verstanden werden müssen, bevor der erste produktive Container in Betrieb geht. Wer diese Grundlagen überspringt, kämpft später mit Datenverlust, Race-Conditions und nicht reproduzierbaren Entwicklungsumgebungen.

15 Min. Lesezeit Init-Skripte · Volumes · Health-Checks · Migrations MySQL 8.4 · PostgreSQL 17 · Redis 7.4

1. Containerisierte Datenbanken vs. klassische Installation

Der entscheidende Unterschied zwischen einer containerisierten Datenbank und einer klassisch installierten Datenbankinstanz liegt im Lebenszyklus. Eine traditionelle MySQL-Installation auf einem Server läuft jahrelang, wird durch Paket-Updates verändert und enthält einen langen historischen Zustand. Eine containerisierte Datenbank ist per Design ephemer: Das Container-Image bleibt unverändert, alle persistenten Daten landen im Volume, und eine Neuerstellung des Containers aus demselben Image sollte jederzeit möglich sein.

Dieses Modell hat erhebliche Vorteile für Entwicklung und Deployment: Jeder Entwickler kann mit einem einzigen docker compose up eine identische Datenbankumgebung starten. Staging- und Produktionsumgebungen nutzen dasselbe Image, was Konfigurationsunterschiede minimiert. Aber es erfordert ein Umdenken bei der Initialisierung: Das Schema, die initialen Daten, die Benutzer und die Konfiguration dürfen nicht manuell nach dem ersten Start eingerichtet werden – sie müssen deklarativ und reproduzierbar definiert sein, sodass containerisierte Datenbanken jederzeit neu erstellt werden können.

Die häufigsten Fehler beim Einstieg in containerisierte Datenbanken sind: Daten in den Container statt in ein Volume schreiben, manuelle Schemaänderungen nach dem ersten Start, fehlende Health-Checks und Race-Conditions, weil die Applikation startet bevor die Datenbank bereit ist. Diese Punkte werden in den folgenden Abschnitten systematisch adressiert.

2. Das Init-Verzeichnis: automatische Datenbankinitialisierung

MySQL, PostgreSQL und MariaDB bieten alle ein Init-Verzeichnis, das beim allerersten Start des Containers automatisch ausgeführt wird. Bei MySQL ist es /docker-entrypoint-initdb.d/. Dateien in diesem Verzeichnis werden in alphabetischer Reihenfolge ausgeführt – SQL-Dateien als Queries, Shell-Skripte als Bash-Skripte. Das ermöglicht es, das Schema, initiale Daten, Benutzer und Berechtigungen vollständig deklarativ zu definieren, ohne dass nach dem ersten Container-Start manuelle Eingriffe nötig sind.

Wichtig: Das Init-Verzeichnis wird nur ausgeführt, wenn das Daten-Volume noch leer ist. Bei einem Container-Neustart mit bestehendem Volume wird das Init-Verzeichnis übersprungen. Das ist das gewünschte Verhalten für containerisierte Datenbanken – die Initialisierung ist idempotent für neue Umgebungen, aber sie überschreibt keine bestehenden Produktionsdaten. Für Schemaänderungen nach dem initialen Setup sind Migrations-Tools zuständig, nicht das Init-Verzeichnis.


# Directory structure for containerized database initialization
# Files are executed in alphabetical order on first start
docker-init/
├── 01-schema.sql        # Create all tables and indexes
├── 02-users.sql         # Create database users and grant permissions
├── 03-seed-data.sql     # Insert required seed/reference data
└── 04-stored-procs.sh   # Shell script to load stored procedures

# 01-schema.sql — Create tables on first init
CREATE DATABASE IF NOT EXISTS `shop` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `shop`;

CREATE TABLE IF NOT EXISTS `products` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `sku`        VARCHAR(64) NOT NULL UNIQUE,
    `name`       VARCHAR(255) NOT NULL,
    `price`      DECIMAL(10,2) NOT NULL DEFAULT 0.00,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX `idx_sku` (`sku`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

# 02-users.sql — Least-privilege user setup
CREATE USER IF NOT EXISTS 'app'@'%' IDENTIFIED BY '${MYSQL_APP_PASSWORD}';
GRANT SELECT, INSERT, UPDATE, DELETE ON `shop`.* TO 'app'@'%';
FLUSH PRIVILEGES;

Ein praktisches Muster für containerisierte Datenbanken: Die Init-Skripte im Repository unter docker/mysql/initdb.d/ ablegen und per Volume-Mount in den Container einbinden. Das stellt sicher, dass jeder Entwickler beim Starten einer neuen Umgebung denselben Initialzustand bekommt. Bei größeren Projekten wie Magento-Shops empfiehlt sich ein Dump der Entwicklungsdatenbank als Ausgangspunkt, ergänzt um anonymisierte Testdaten für den Entwicklungsalltag.

3. Volume-Persistenz: Daten überleben Container-Neustarts

Das Volume ist der kritischste Aspekt von containerisierten Datenbanken. Ohne Volume gehen alle Daten beim Entfernen des Containers verloren – docker compose down ohne -v-Flag entfernt den Container, aber nicht das Volume. Mit docker compose down -v wird auch das Volume gelöscht. Diese Unterscheidung muss jedem Operator bekannt sein, der mit containerisierten Datenbanken arbeitet. In der Produktion empfiehlt sich die Verwendung von Named Volumes statt Bind-Mounts für Datenbankdaten, weil Named Volumes vom Docker-Daemon verwaltet werden und nicht von der Verzeichnisstruktur des Hosts abhängen.

Für containerisierte Datenbanken in der Produktion gilt: Das Volume-Verzeichnis auf dem Host muss in die Backup-Strategie einbezogen sein. Named Volumes liegen unter /var/lib/docker/volumes/. Ein konsistentes Backup erfordert entweder einen Lock der Datenbank oder den Einsatz von Tools wie mysqldump, pg_dump oder Percona XtraBackup, die einen konsistenten Snapshot erstellen, ohne die Datenbank vollständig zu sperren. Das Volume allein ist kein Backup.

4. Health-Check-Synchronisation mit abhängigen Services

Race-Conditions zwischen dem Datenbankcontainer und dem Applikationscontainer sind eines der häufigsten Probleme bei containerisierten Datenbanken. Docker Compose startet Services entsprechend der depends_on-Direktive, wartet aber standardmäßig nur darauf, dass der Container gestartet ist – nicht darauf, dass die Datenbank Verbindungen akzeptiert. Ein MySQL-Container kann mehrere Sekunden nach dem Start noch im Initialisierungsmodus sein, in dem keine Verbindungen möglich sind.

Die Lösung für containerisierte Datenbanken ist depends_on mit der condition: service_healthy-Option. Docker Compose wartet dann, bis der Health-Check des Datenbankcontainers den Status Healthy zurückmeldet, bevor der abhängige Service gestartet wird. Dafür muss der Datenbankcontainer einen funktionierenden Health-Check konfiguriert haben. Alternativ können Wait-Skripte wie wait-for-it.sh oder dockerize eingesetzt werden, die im Applikationscontainer laufen und auf die Verfügbarkeit der Datenbankverbindung warten.


# docker-compose.yml — Health-synchronized startup for containerized databases
version: "3.9"
services:
  mysql:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password
      MYSQL_DATABASE: shop
    volumes:
      # Named volume for data persistence across container recreations
      - mysql_data:/var/lib/mysql
      # Init scripts — executed only on empty volume (first start)
      - ./docker/mysql/initdb.d:/docker-entrypoint-initdb.d:ro
    healthcheck:
      # mysqladmin ping confirms the server accepts connections
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "--silent"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 60s

  php:
    image: mironsoft/magento-php:8.4-fpm
    depends_on:
      mysql:
        # Wait until MySQL health check reports healthy
        condition: service_healthy
      redis:
        condition: service_healthy

  redis:
    image: redis:7.4-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      retries: 5

volumes:
  mysql_data:
    # Named volume managed by Docker daemon
    driver: local

5. Umgebungsvariablen und Credentials sicher konfigurieren

Datenbankpasswörter in containerisierten Datenbanken über Umgebungsvariablen zu übergeben, ist die Standardmethode – aber nur sicher, wenn die Werte nicht im docker-compose.yml im Klartext stehen. MySQL und PostgreSQL unterstützen die _FILE-Variante für sensitive Werte: MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_root_password liest das Passwort aus einer Datei statt aus einer Umgebungsvariable. Diese Dateien werden als Docker Secrets oder per Read-only-Mount bereitgestellt.

Eine häufig übersehene Sicherheitslücke bei containerisierten Datenbanken: MySQL-Images erstellen standardmäßig einen Root-Benutzer ohne Host-Beschränkung, wenn MYSQL_ROOT_PASSWORD gesetzt ist. Für die Applikation sollte immer ein Benutzer mit minimalen Berechtigungen verwendet werden, der nur auf die spezifische Datenbank Zugriff hat. Das Root-Passwort dient ausschließlich für administrative Aufgaben. Diese Separation ist Teil der Initialisierungsskripte in /docker-entrypoint-initdb.d/.

6. Schema-Migrationen in containerisierten Umgebungen

Das Init-Verzeichnis behandelt den Initialzustand von containerisierten Datenbanken. Schemaänderungen im laufenden Betrieb erfordern ein dediziertes Migrations-Tool. Flyway, Liquibase oder Applikations-eigene Migrations (Laravel Migrations, Magento db_schema.xml, Django Migrations) verwalten den Versionszustand des Schemas und führen nur die Änderungen aus, die noch nicht angewendet wurden. Das Migrations-Tool wird idealerweise als eigener Container gestartet, der die Migration ausführt und sich danach beendet.

Das Muster für containerisierte Datenbanken: Ein Migrations-Container mit depends_on: mysql: condition: service_healthy führt die Migrationen aus. Der Applikationscontainer hängt wiederum vom Migrations-Container ab: depends_on: migrations: condition: service_completed_successfully. So ist garantiert, dass das Schema immer aktuell ist, bevor die erste Applikationsanfrage verarbeitet wird. Dieses Muster funktioniert sowohl für erste Deployments als auch für Updates mit Schema-Änderungen.

7. Backup und Restore für containerisierte Datenbanken

Das Backup-Muster für containerisierte Datenbanken unterscheidet sich von klassischen Datenbankbackups: Statt direkt auf das Datenverzeichnis zuzugreifen, wird der Backup-Befehl im laufenden Container ausgeführt. docker exec ist dabei das Werkzeug der Wahl. Ein mysqldump lässt sich direkt in eine komprimierte Datei auf dem Host schreiben, ohne temporäre Dateien im Container zu erstellen.

Für Produktionsumgebungen mit containerisierten Datenbanken empfiehlt sich ein dedizierter Backup-Container als Cron-Job oder als Docker-Service, der täglich einen Dump erstellt, ihn komprimiert und auf einen externen Speicher überträgt. Das Volume-Verzeichnis selbst kann als physisches Backup dienen, aber nur wenn die Datenbank entweder gestoppt wurde oder das Backup-Tool transaktionskonsistente Snapshots unterstützt.


#!/usr/bin/env bash
# backup-database.sh — Backup containerized MySQL database
set -euo pipefail

CONTAINER="project_mysql_1"
BACKUP_DIR="/var/backups/mysql"
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/shop_${DATE}.sql.gz"

mkdir -p "$BACKUP_DIR"

# Run mysqldump inside the container, pipe output directly to gzip
docker exec "$CONTAINER" mysqldump \
  --single-transaction \
  --routines \
  --triggers \
  --databases shop \
  | gzip -9 > "$BACKUP_FILE"

echo "[OK] Backup written to: $BACKUP_FILE"
echo "[OK] Size: $(du -sh "$BACKUP_FILE" | cut -f1)"

# Keep only last 7 daily backups
find "$BACKUP_DIR" -name "shop_*.sql.gz" -mtime +7 -delete

# Restore example:
# gunzip -c "$BACKUP_FILE" | docker exec -i "$CONTAINER" mysql -u root -p shop

8. Performance-Tuning für containerisierte Datenbanken

MySQL und PostgreSQL sind für klassische Server-Installationen mit dediziertem RAM optimiert. In containerisierten Datenbanken läuft der Datenbankprozess in einem Container, der sich RAM mit anderen Containern teilt. Die Standardkonfiguration von MySQL geht von einem Server mit 128 MB RAM aus, was für einen Produktions-Shop mit hunderten Verbindungen völlig unzureichend ist. innodb_buffer_pool_size sollte auf 60–70% des für den Container reservierten Speichers gesetzt werden – bei einem Container mit 4 GB Limit also auf 2,4–2,8 GB.

Für containerisierte Datenbanken empfiehlt sich eine Custom-Konfigurationsdatei, die als Read-only-Volume in den Container gemountet wird. So bleibt die Konfiguration außerhalb des Images, versionskontrolliert und umgebungsspezifisch anpassbar. Redis-Container profitieren von der Einstellung maxmemory und einer passenden maxmemory-policy (für E-Commerce meist allkeys-lru), die verhindert, dass Redis unbegrenzt Speicher belegt und bei Erschöpfung Daten verliert.

9. Initialisierungsstrategien im direkten Vergleich

Es gibt mehrere Ansätze zur Initialisierung von containerisierten Datenbanken, die sich in Komplexität, Wartbarkeit und Eignung für verschiedene Umgebungen unterscheiden.

Strategie Einsatzgebiet Vorteil Nachteil
Init-Verzeichnis Entwicklung, neue Deployments Automatisch, keine Intervention Nur bei leerem Volume
Migrations-Container Produktion, kontinuierliche Updates Versioniert, rollbackfähig Zusätzliche Infrastruktur
Volume-Import Daten-Migration, Staging-Refresh Schnell bei großen Datenmengen Manueller Schritt
Custom Image mit vorinitialisiertem Volume CI/CD, Test-Umgebungen Sofort startbereit Image-Größe, Daten im Image
Applikations-eigene Migration (Magento, Django) Applikations-Updates Integriert in Deploy-Prozess Kopplung an Applikation

Für die meisten Produktionsprojekte mit containerisierten Datenbanken empfiehlt sich eine Kombination: Init-Verzeichnis für den initialen Datenbankzustand, Migrations-Tool für alle nachfolgenden Schemaänderungen. Das Init-Verzeichnis stellt sicher, dass neue Umgebungen reproduzierbar erstellt werden können. Das Migrations-Tool verwaltet die Evolution des Schemas im laufenden Betrieb. Diese Kombination deckt den gesamten Lebenszyklus ab.

Mironsoft

Docker-Datenbanksetup, Migrations-Automatisierung und Backup-Strategien

Containerisierte Datenbanken produktionsreif aufsetzen?

Wir richten containerisierte Datenbanken mit Init-Skripten, Health-Check-Synchronisation, sicheren Credentials und vollständiger Backup-Strategie für euer Projekt ein – für Magento, Shopware und kundenspezifische APIs.

Datenbanksetup

Init-Skripte, Health-Checks und Migrations-Container für reproduzierbare Umgebungen

Backup-Automatisierung

Tägliche mysqldump-Backups mit Retention-Policy und Off-Site-Transfer

Performance-Tuning

InnoDB-Buffer-Pool und Redis-Konfiguration für containerisierte Workloads optimieren

10. Zusammenfassung

Containerisierte Datenbanken richtig initialisieren bedeutet, den gesamten Lebenszyklus der Datenbank – von der ersten Erstellung bis zu Schema-Updates und Backups – reproduzierbar und automatisiert zu gestalten. Das Init-Verzeichnis /docker-entrypoint-initdb.d/ setzt den initialen Datenbankzustand für neue Umgebungen. Named Volumes sichern die Datenpersistenz über Container-Neustarts hinaus. Health-Checks mit service_healthy-Conditions in depends_on verhindern Race-Conditions beim Start.

Credentials gehören aus dem docker-compose.yml in Docker Secrets oder _FILE-Variablen. Schemamigrationen werden durch dedizierte Migrations-Container im Deploy-Prozess verwaltet. Backups werden über docker exec mysqldump erstellt und extern gespeichert. Performance-Konfiguration kommt aus Read-only-gemounteten Konfigurationsdateien außerhalb des Images. Wer diese Punkte konsequent umsetzt, hat eine Datenbankinfrastruktur, die sowohl in der Entwicklung als auch in der Produktion zuverlässig und reproduzierbar ist.

Containerisierte Datenbanken — Das Wichtigste auf einen Blick

Init & Persistenz

/docker-entrypoint-initdb.d/ für Initialzustand. Named Volumes für Persistenz. Init-Verzeichnis läuft nur bei leerem Volume.

Health-Check-Synchronisation

depends_on: condition: service_healthy verhindert Race-Conditions. Datenbankcontainer braucht Health-Check mit ausreichendem start_period.

Credentials & Migrationen

MYSQL_ROOT_PASSWORD_FILE statt Klartext. Migrations-Container für Schemaänderungen im laufenden Betrieb.

Backup & Tuning

docker exec mysqldump --single-transaction für konsistente Backups. InnoDB-Buffer-Pool auf 70% des Container-Speicherlimits setzen.

11. FAQ: Containerisierte Datenbanken richtig initialisieren

1Wann läuft das Init-Verzeichnis?
Nur beim ersten Start mit leerem Volume. Bei Container-Neustarts wird es übersprungen – verhindert versehentliches Überschreiben von Produktionsdaten.
2Bind-Mount vs. Named Volume?
Named Volumes werden vom Docker-Daemon unter /var/lib/docker/volumes/ verwaltet – portabler und für Datenbankdaten bevorzugt gegenüber Bind-Mounts.
3Race-Conditions zwischen App und DB verhindern?
depends_on mit condition: service_healthy. Datenbankcontainer braucht Health-Check mit mysqladmin ping oder pg_isready.
4Gehen Daten bei docker compose down verloren?
Nein, Named Volumes bleiben erhalten. Nur docker compose down -v löscht auch die Volumes – niemals unbeabsichtigt ausführen.
5Passwörter sicher übergeben?
MYSQL_ROOT_PASSWORD_FILE statt MYSQL_ROOT_PASSWORD verwenden. Datei als Docker Secret oder Read-only-Volume bereitstellen.
6Konsistentes Backup erstellen?
docker exec container mysqldump --single-transaction – erstellt konsistenten InnoDB-Snapshot ohne Tabellen-Lock, Ausgabe direkt in gzip pipen.
7InnoDB-Buffer-Pool im Container tunen?
my.cnf als Read-only-Volume in /etc/mysql/conf.d/ mounten. innodb_buffer_pool_size auf 60-70% des mem_limit des Containers setzen.
8PostgreSQL gleich wie MySQL initialisieren?
Ja – gleiche /docker-entrypoint-initdb.d/ Methode. Health-Check nutzt pg_isready statt mysqladmin ping. Grundprinzip identisch.
9Was ist ein Migrations-Container?
Kurzlebiger Container für Flyway/Liquibase – führt Migrationen aus und beendet sich. App wartet mit condition: service_completed_successfully.
10Wie lange start_period für MySQL?
Mindestens 60s für einfache DBs. Bei großen Init-Dumps oder langer Crash-Recovery 120s oder mehr – großzügig dimensionieren.