Docker für PHP-Entwicklung
PHP & DevOps

Docker für PHP-Entwicklung: Praxisguide für Dev-Umgebungen

Carola Schulte
5. Januar 2026
10 min

"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

ExtensionWofürInstallation
pdo_mysqlMySQL-Datenbankzugriffdocker-php-ext-install pdo_mysql
gdBildbearbeitungMit freetype/jpeg konfigurieren
intlInternationalisierungBenötigt icu-dev
zipZIP-ArchiveBenötigt libzip-dev
redisRedis-CacheVia PECL
opcachePerformancedocker-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 ./src sind 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.