Docker für PHP-Entwicklung: Praxisguide für Dev-Umgebungen
"Works on my machine" – der Klassiker unter den Entwickler-Ausreden. Docker löst dieses Problem seit Jahren zuverlässig. Trotzdem sehe ich bei vielen PHP-Projekten noch immer XAMPP, MAMP oder direkte Installationen auf dem Entwicklerrechner. Zeit, das zu ändern.
TL;DR – Die Kurzfassung
- docker-compose für PHP + MySQL + Nginx in 5 Minuten
- Volumes für Live-Reload ohne Container-Neustart
- PHP-Extensions: Der wichtigste Teil – gd, pdo_mysql, intl richtig installieren
- Composer im Container oder auf dem Host – beides möglich
- Performance-Tipps für Mac/Windows (Docker kann langsam sein)
Anmerkung: Ich schreibe hier docker-compose – das funktioniert weiterhin, aber docker compose (als Plugin) ist inzwischen der offizielle Standard. Beide sind austauschbar.
Warum Docker für PHP-Entwicklung?
Bevor wir in die Technik einsteigen: Warum überhaupt Docker?
Die Probleme ohne Docker
- PHP-Version gebunden: Der Rechner hat PHP 8.2, das Projekt braucht 8.1
- Extension-Chaos: "Bei mir fehlt gd" – "Bei mir nicht"
- MySQL vs. MariaDB: Unterschiedliche Versionen, unterschiedliche Bugs
- Onboarding: Neue Entwickler brauchen Stunden für die Einrichtung
- Production-Parity: Lokal läuft es, auf dem Server nicht
Die Lösung mit Docker
- Exakte PHP-Version pro Projekt konfigurierbar
- Alle Extensions im Dockerfile definiert – für alle gleich
- Datenbank-Version fixiert, keine Überraschungen
- Ein Befehl:
docker-compose up– fertig - Gleiche Umgebung wie auf dem Server (oder sehr nah dran)
Das Grund-Setup: PHP + MySQL + Nginx
Hier ist ein minimales, aber produktionstaugliches Setup für PHP-Entwicklung:
Projektstruktur
mein-projekt/
├── docker/
│ ├── nginx/
│ │ └── default.conf
│ └── php/
│ └── Dockerfile
├── src/
│ └── public/
│ └── index.php
├── docker-compose.yml
└── .env
docker-compose.yml
# version: '3.8' # Nicht mehr nötig – Compose v2 ignoriert das Feld
services:
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
php:
build:
context: ./docker/php
volumes:
- ./src:/var/www/html
environment:
- PHP_MEMORY_LIMIT=256M
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-secret}
MYSQL_DATABASE: ${DB_DATABASE:-app}
MYSQL_USER: ${DB_USER:-app}
MYSQL_PASSWORD: ${DB_PASSWORD:-secret}
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306" # Nur für lokale Tools (TablePlus, DBeaver) – Container untereinander brauchen das nicht
volumes:
mysql_data:
Hinweis: Der MySQL-Port wird hier nach außen exponiert, damit lokale Tools wie TablePlus oder DBeaver zugreifen können. Für reine Container-zu-Container-Kommunikation ist das nicht nötig – die Container erreichen sich über den Service-Namen (mysql).
Nginx-Konfiguration
Datei: docker/nginx/default.conf
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
}
location ~ /\.ht {
deny all;
}
}
PHP Dockerfile
Datei: docker/php/Dockerfile
# PHP-Version als Build-Argument – pro Projekt fixierbar
ARG PHP_VERSION=8.3
FROM php:${PHP_VERSION}-fpm-alpine
# System-Dependencies für PHP-Extensions
RUN apk add --no-cache \
freetype-dev \
libjpeg-turbo-dev \
libpng-dev \
libzip-dev \
icu-dev \
oniguruma-dev
# PHP-Extensions installieren
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd \
pdo_mysql \
mysqli \
zip \
intl \
mbstring \
opcache
# Composer installieren
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
Mit diesem Setup: docker-compose up -d – und die App läuft unter http://localhost:8080.
PHP-Extensions: Der wichtigste Teil
Das ist der Punkt, an dem die meisten Docker-Setups scheitern. PHP-Extensions in Docker zu installieren ist anders als auf einem normalen Server.
Die drei Methoden
1. docker-php-ext-install (für Kern-Extensions)
# Einfache Extensions ohne Dependencies
RUN docker-php-ext-install pdo_mysql mysqli opcache
# Extensions mit Konfiguration
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd
2. PECL (für Community-Extensions)
# Redis, APCu, etc.
RUN pecl install redis apcu \
&& docker-php-ext-enable redis apcu
3. Manuelle Kompilierung (für spezielle Fälle)
# Beispiel: imagick
RUN apk add --no-cache imagemagick-dev \
&& pecl install imagick \
&& docker-php-ext-enable imagick
Häufig benötigte Extensions
| Extension | Wofür | Installation |
|---|---|---|
| pdo_mysql | MySQL-Datenbankzugriff | docker-php-ext-install pdo_mysql |
| gd | Bildbearbeitung | Mit freetype/jpeg konfigurieren |
| intl | Internationalisierung | Benötigt icu-dev |
| zip | ZIP-Archive | Benötigt libzip-dev |
| redis | Redis-Cache | Via PECL |
| opcache | Performance | docker-php-ext-install opcache |
Vollständiges Beispiel-Dockerfile
FROM php:8.3-fpm-alpine
# System-Abhängigkeiten
RUN apk add --no-cache \
freetype-dev \
libjpeg-turbo-dev \
libpng-dev \
libzip-dev \
icu-dev \
oniguruma-dev \
libxml2-dev \
curl-dev \
linux-headers \
$PHPIZE_DEPS
# Kern-Extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd \
pdo_mysql \
mysqli \
zip \
intl \
mbstring \
opcache \
bcmath \
soap \
exif
# PECL-Extensions
RUN pecl install redis apcu \
&& docker-php-ext-enable redis apcu
# Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# PHP-Konfiguration
RUN echo "memory_limit=256M" > /usr/local/etc/php/conf.d/memory.ini \
&& echo "upload_max_filesize=64M" >> /usr/local/etc/php/conf.d/memory.ini \
&& echo "post_max_size=64M" >> /usr/local/etc/php/conf.d/memory.ini
WORKDIR /var/www/html
Volumes für Live-Entwicklung
Der Clou bei der Entwicklung: Code-Änderungen sofort sichtbar, ohne Container-Neustart.
Bind Mounts vs. Named Volumes
volumes:
# Bind Mount: Lokaler Ordner → Container (für Code)
- ./src:/var/www/html
# Named Volume: Persistente Daten (für Datenbank)
- mysql_data:/var/lib/mysql
Regel: Code als Bind Mount, Daten als Named Volume.
Warum das wichtig ist
- Bind Mount: Änderungen in
./srcsind sofort im Container sichtbar - Named Volume: Datenbank-Daten überleben Container-Neustarts
- Kein Rebuild nötig: PHP-Dateien ändern → Browser refreshen → fertig
Probleme und Lösungen
Problem: Berechtigungen (Linux)
# Im Dockerfile: User-ID anpassen
ARG UID=1000
ARG GID=1000
RUN addgroup -g ${GID} app && adduser -u ${UID} -G app -s /bin/sh -D app
USER app
Achtung: Wenn die App Schreibrechte braucht (storage, cache, logs), müssen ownership und permissions stimmen. Typisch: chown -R app:app storage und chmod -R 775 storage. Sonst kommen Fehler wie "Permission denied" beim Schreiben von Logs oder Cache-Dateien.
Problem: Performance (Mac/Windows)
Docker Desktop auf Mac/Windows ist bei Bind Mounts langsam. Lösungen:
- :cached Flag:
- ./src:/var/www/html:cached - Docker Desktop 4.x+: VirtioFS statt gRPC-FUSE aktivieren
- vendor-Ordner ausschließen: Als Named Volume mounten
# Performance-optimiert für Mac/Windows
volumes:
- ./src:/var/www/html:cached
- vendor:/var/www/html/vendor # vendor als Named Volume
volumes:
vendor:
Composer: Im Container oder auf dem Host?
Zwei Philosophien, beide funktionieren:
Option 1: Composer im Container
# Einmalig
docker-compose exec php composer install
# Neues Paket
docker-compose exec php composer require monolog/monolog
Vorteile: Exakt gleiche PHP-Version, keine lokale PHP-Installation nötig.
Option 2: Composer auf dem Host
# Lokal ausführen
composer install
Vorteile: Schneller, IDE-Autovervollständigung funktioniert direkt.
Meine Empfehlung
Für die Entwicklung: Composer auf dem Host (schneller, bessere IDE-Integration).
Für CI/CD: Composer im Container (reproduzierbar).
Praktische Tipps aus dem Alltag
1. Shell-Alias für häufige Befehle
# In ~/.bashrc oder ~/.zshrc
alias dc="docker-compose"
alias dce="docker-compose exec"
alias php-docker="docker-compose exec php php"
alias composer-docker="docker-compose exec php composer"
2. Makefile für komplexe Befehle
# Makefile
.PHONY: up down logs shell
up:
docker-compose up -d
down:
docker-compose down
logs:
docker-compose logs -f
shell:
docker-compose exec php sh
install:
docker-compose exec php composer install
migrate:
docker-compose exec php php artisan migrate
Dann einfach: make up, make shell, make migrate.
3. .env für projektspezifische Konfiguration
# .env
DB_DATABASE=meine_app
DB_USER=app_user
DB_PASSWORD=geheim123
DB_ROOT_PASSWORD=root_geheim
PHP_VERSION=8.3
4. Mehrere PHP-Versionen testen
# docker-compose.override.yml für PHP 8.1 Tests
services:
php:
build:
args:
PHP_VERSION: 8.1
5. Logs vernünftig ausgeben
# Alle Logs
docker-compose logs -f
# Nur PHP
docker-compose logs -f php
# Letzte 100 Zeilen
docker-compose logs --tail=100 php
Erweiterungsmöglichkeiten
Das Basis-Setup lässt sich einfach erweitern:
Redis hinzufügen
# In docker-compose.yml
redis:
image: redis:alpine
ports:
- "6379:6379"
Mailhog für E-Mail-Tests
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web-UI
phpMyAdmin
phpmyadmin:
image: phpmyadmin
environment:
PMA_HOST: mysql
ports:
- "8081:80"
Xdebug (nur für Debugging)
# Im Dockerfile
RUN pecl install xdebug && docker-php-ext-enable xdebug
# xdebug.ini
RUN echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/xdebug.ini
Hinweis: Xdebug nur in der Entwicklung aktivieren – es verlangsamt PHP erheblich.
Fazit: Docker lohnt sich
Der initiale Aufwand für ein Docker-Setup zahlt sich schnell aus:
- Neue Teammitglieder sind in Minuten statt Stunden produktiv
- PHP-Versions-Wechsel ist ein Einzeiler im Dockerfile
- Extensions sind dokumentiert und reproduzierbar
- "Bei mir geht's" wird zur Vergangenheit
Ja, Docker hat eine Lernkurve. Ja, auf Mac/Windows gibt es Performance-Themen. Aber die Vorteile überwiegen – besonders im Team.
"Ein gutes Docker-Setup ist wie eine gute Dokumentation: Am Anfang Aufwand, danach nur noch Gewinn."
Brauchen Sie Unterstützung bei der Docker-Migration Ihrer PHP-Projekte? Ich helfe gerne – von der initialen Einrichtung bis zur Optimierung bestehender Setups.
Weitere interessante Artikel
Debian Server Setup
Server-Grundkonfiguration für PHP-Anwendungen.
Caching-Strategien
Redis, APCu und Query-Cache richtig einsetzen.
Background Jobs in PHP
Queues und Worker-Prozesse implementieren.