---
# === IDENTITY ===
id: software/devops/docker-lamp-stack/2026
canonical_question: "Docker Compose reference: LAMP Stack (Apache, MySQL, PHP)"
aliases:
  - "docker compose LAMP stack setup"
  - "docker PHP Apache MySQL development environment"
  - "containerized LAMP stack with docker-compose"
  - "docker-compose.yml for PHP MySQL Apache"
  - "docker LAMP stack tutorial"
  - "PHP Apache MySQL Docker containers"
  - "docker compose web development stack"
  - "dockerize LAMP application"
entity_type: software_reference
domain: software > devops > Docker LAMP Stack
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.93
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "MySQL 8.4 LTS released (2024-04); Docker Compose V2 replaces V1 (docker compose replaces docker-compose, 2023); PHP 8.3 released (2023-11)"
  next_review: 2026-08-26
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "NEVER use MYSQL_ALLOW_EMPTY_PASSWORD=yes in production -- always set a strong root password or use Docker secrets"
  - "NEVER hardcode database passwords in docker-compose.yml for production -- use .env files or Docker secrets (_FILE suffix)"
  - "ALWAYS use named volumes for MySQL data persistence -- bind mounts risk permission issues and data loss"
  - "NEVER expose MySQL port 3306 to 0.0.0.0 in production -- bind to 127.0.0.1 or remove the port mapping entirely"
  - "ALWAYS use the Docker service name (e.g., 'db') as the MySQL hostname in PHP -- never use 'localhost' or '127.0.0.1' from within containers"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need a LEMP stack (Nginx instead of Apache)"
    use_instead: "software/devops/docker-lemp-stack/2026"
  - condition: "Need a production Kubernetes deployment, not Docker Compose"
    use_instead: "software/devops/kubernetes-php-deployment/2026"
  - condition: "Need WordPress specifically (not generic PHP)"
    use_instead: "software/devops/docker-wordpress/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "php_version"
    question: "Which PHP version do you need?"
    type: choice
    options: ["8.3", "8.2", "8.1", "8.0", "7.4"]
  - key: "mysql_version"
    question: "Which MySQL version do you need?"
    type: choice
    options: ["8.4 LTS", "8.0", "9.x Innovation", "MariaDB 11"]
  - key: "environment"
    question: "Is this for development or production?"
    type: choice
    options: ["development", "production"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/devops/docker-lamp-stack/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/devops/docker-lemp-stack/2026"
      label: "Docker LEMP Stack (Nginx + MySQL + PHP)"
    - id: "software/devops/docker-wordpress/2026"
      label: "Docker WordPress Setup"
  solves: []
  alternative_to:
    - id: "software/devops/docker-lemp-stack/2026"
      label: "Docker LEMP Stack (Nginx replaces Apache)"
  often_confused_with:
    - id: "software/devops/docker-lemp-stack/2026"
      label: "LEMP Stack (uses Nginx, not Apache)"

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "php - Official Image"
    author: Docker
    url: https://hub.docker.com/_/php
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src2
    title: "mysql - Official Image"
    author: Docker
    url: https://hub.docker.com/_/mysql
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src3
    title: "phpmyadmin - Official Image"
    author: Docker
    url: https://hub.docker.com/_/phpmyadmin
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src4
    title: "How to Set Up a LAMP Stack with Docker Compose"
    author: Linuxiac
    url: https://linuxiac.com/how-to-set-up-lamp-stack-with-docker-compose/
    type: technical_blog
    published: 2025-10-23
    reliability: moderate_high
  - id: src5
    title: "Docker Compose for Development: Setting Up a Complete LAMP Stack"
    author: DEV Community
    url: https://dev.to/caffinecoder54/docker-compose-for-development-setting-up-a-complete-lamp-stack-16h2
    type: community_resource
    published: 2025-05-15
    reliability: moderate_high
  - id: src6
    title: "Best practices for building a production-ready Dockerfile for PHP applications"
    author: Snyk
    url: https://snyk.io/blog/building-production-ready-dockerfile-php/
    type: technical_blog
    published: 2024-08-01
    reliability: high
  - id: src7
    title: "Set environment variables in Docker Compose"
    author: Docker
    url: https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
---

# Docker Compose LAMP Stack: Apache, MySQL, PHP Reference

## TL;DR

- **Bottom line**: A Docker Compose LAMP stack uses three services -- `php:8.3-apache` for PHP+Apache, `mysql:8.4` for the database, and optionally `phpmyadmin` for DB management -- connected via a Docker bridge network with named volumes for data persistence.
- **Key tool/command**: `docker compose up -d --build`
- **Watch out for**: Using `localhost` as the MySQL host inside PHP containers -- you must use the Docker Compose service name (e.g., `db`) instead.
- **Works with**: Docker Engine 24+, Docker Compose V2, PHP 8.0-8.3, MySQL 8.0/8.4/9.x, all major OS (Linux, macOS, Windows with WSL2).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER use `MYSQL_ALLOW_EMPTY_PASSWORD=yes` in production -- always set a strong root password or use Docker secrets
- NEVER hardcode database passwords in `docker-compose.yml` for production -- use `.env` files or Docker secrets (`_FILE` suffix)
- ALWAYS use named volumes for MySQL data persistence -- bind mounts risk permission issues and data loss
- NEVER expose MySQL port 3306 to `0.0.0.0` in production -- bind to `127.0.0.1` or remove the port mapping entirely
- ALWAYS use the Docker service name (e.g., `db`) as the MySQL hostname in PHP -- never use `localhost` or `127.0.0.1` from within containers

## Quick Reference

| Service | Image | Ports | Volumes | Key Env |
|---|---|---|---|---|
| php-apache | `php:8.3-apache` | `8080:80` | `./src:/var/www/html` | `APACHE_DOCUMENT_ROOT=/var/www/html` |
| db (MySQL) | `mysql:8.4` | `3306:3306` (dev only) | `mysql_data:/var/lib/mysql` | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE`, `MYSQL_USER`, `MYSQL_PASSWORD` |
| phpmyadmin | `phpmyadmin:latest` | `8081:80` | -- | `PMA_HOST=db`, `PMA_PORT=3306` |

**PHP Extension Install Helpers** (inside Dockerfile): [src1]

| Helper Script | Purpose | Example |
|---|---|---|
| `docker-php-ext-install` | Install bundled PHP extensions | `docker-php-ext-install pdo_mysql mysqli` |
| `docker-php-ext-configure` | Configure extension before installing | `docker-php-ext-configure gd --with-jpeg` |
| `docker-php-ext-enable` | Enable a PECL-installed extension | `docker-php-ext-enable redis` |

**MySQL Initialization** (runs only on first start): [src2]

| Method | Mount Path | Supported Extensions |
|---|---|---|
| SQL scripts | `/docker-entrypoint-initdb.d/` | `.sh`, `.sql`, `.sql.gz`, `.sql.bz2`, `.sql.xz`, `.sql.zst` |
| Custom config | `/etc/mysql/conf.d/` | `.cnf` files |
| Secrets | `/run/secrets/<name>` | Use `MYSQL_ROOT_PASSWORD_FILE` env var |

**Docker Compose V2 Commands**:

| Action | Command |
|---|---|
| Start stack | `docker compose up -d --build` |
| Stop stack | `docker compose down` |
| Stop + delete volumes | `docker compose down -v` |
| View logs | `docker compose logs -f [service]` |
| Shell into container | `docker compose exec php-apache bash` |
| Rebuild single service | `docker compose build php-apache` |

## Decision Tree

```
START: What kind of PHP + MySQL Docker setup do you need?
├── Development environment with hot-reload?
│   ├── YES → Use bind mount (./src:/var/www/html) + phpMyAdmin service
│   └── NO ↓
├── Production deployment?
│   ├── YES → Use multi-stage Dockerfile, COPY app code (no bind mounts),
│   │         Docker secrets for passwords, no phpMyAdmin, no exposed DB port
│   └── NO ↓
├── Need PHP-FPM (separate Apache/Nginx)?
│   ├── YES → Use php:8.3-fpm image + separate httpd or nginx container
│   └── NO ↓
├── Need multiple PHP versions side-by-side?
│   ├── YES → Create separate services with different php:X.Y-apache images
│   └── NO ↓
└── DEFAULT → Use php:8.3-apache all-in-one image with MySQL 8.4 LTS
```

## Step-by-Step Guide

### 1. Create the project directory structure

Set up the directory layout for your LAMP project. [src4]

```bash
mkdir -p lamp-project/{src,mysql/init,php}
cd lamp-project
```

Expected structure:

```
lamp-project/
├── docker-compose.yml
├── .env
├── php/
│   └── Dockerfile
├── mysql/
│   └── init/
│       └── 01-create-tables.sql
└── src/
    └── index.php
```

**Verify**: `ls -la lamp-project/` -- all directories should exist.

### 2. Create the PHP Dockerfile

Build a custom image based on `php:8.3-apache` with required extensions. [src1]

```dockerfile
# php/Dockerfile
FROM php:8.3-apache

# Install system dependencies for PHP extensions
RUN apt-get update && apt-get install -y \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    libzip-dev \
    unzip \
    && rm -rf /var/lib/apt/lists/*

# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
    pdo_mysql \
    mysqli \
    gd \
    zip \
    opcache

# Enable Apache modules
RUN a2enmod rewrite headers expires

# Copy custom PHP config
COPY php.ini /usr/local/etc/php/conf.d/custom.ini

# Set working directory
WORKDIR /var/www/html

# Apache runs as www-data by default
RUN chown -R www-data:www-data /var/www/html
```

**Verify**: `docker compose build php-apache` -- should complete without errors.

### 3. Create the PHP configuration file

Tune PHP settings for development or production. [src6]

```ini
; php/php.ini -- development settings
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
log_errors = On
error_log = /dev/stderr

; Performance
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300

; OPcache (enable in production, optional in dev)
opcache.enable = 1
opcache.memory_consumption = 128
opcache.max_accelerated_files = 10000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 0
```

**Verify**: After starting the stack, run `docker compose exec php-apache php -i | grep display_errors` -- should show `On`.

### 4. Create the docker-compose.yml

Define all services, networks, and volumes. [src4] [src5]

```yaml
# docker-compose.yml
services:
  php-apache:
    build:
      context: ./php
      dockerfile: Dockerfile
    container_name: lamp-php
    ports:
      - "8080:80"
    volumes:
      - ./src:/var/www/html
    depends_on:
      db:
        condition: service_healthy
    environment:
      - DB_HOST=db
      - DB_NAME=${MYSQL_DATABASE}
      - DB_USER=${MYSQL_USER}
      - DB_PASS=${MYSQL_PASSWORD}
    networks:
      - lamp-network
    restart: unless-stopped

  db:
    image: mysql:8.4
    container_name: lamp-mysql
    ports:
      - "127.0.0.1:3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/init:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - lamp-network
    restart: unless-stopped

  phpmyadmin:
    image: phpmyadmin:latest
    container_name: lamp-phpmyadmin
    ports:
      - "8081:80"
    environment:
      PMA_HOST: db
      PMA_PORT: 3306
      UPLOAD_LIMIT: 64M
    depends_on:
      db:
        condition: service_healthy
    networks:
      - lamp-network
    restart: unless-stopped

networks:
  lamp-network:
    driver: bridge

volumes:
  mysql_data:
```

**Verify**: `docker compose config` -- should print the resolved config without errors.

### 5. Create the .env file

Store sensitive credentials outside the compose file. [src7]

```bash
# .env
MYSQL_ROOT_PASSWORD=change_me_root_2026
MYSQL_DATABASE=lamp_app
MYSQL_USER=lamp_user
MYSQL_PASSWORD=change_me_user_2026
```

**Verify**: `docker compose config | grep MYSQL_DATABASE` -- should show `lamp_app`.

### 6. Create an initialization SQL script

Pre-populate the database on first start. [src2]

```sql
-- mysql/init/01-create-tables.sql
-- Runs automatically on first container start only

CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO users (username, email) VALUES
    ('admin', 'admin@example.com'),
    ('testuser', 'test@example.com');
```

**Verify**: After starting the stack, run `docker compose exec db mysql -u lamp_user -p lamp_app -e "SELECT * FROM users;"`.

### 7. Create a test PHP file

Verify PHP, Apache, and MySQL connectivity. [src1]

```php
<?php
// src/index.php
$host = getenv('DB_HOST') ?: 'db';
$db   = getenv('DB_NAME') ?: 'lamp_app';
$user = getenv('DB_USER') ?: 'lamp_user';
$pass = getenv('DB_PASS') ?: '';

echo "<h1>LAMP Stack Running</h1>";
echo "<p>PHP Version: " . phpversion() . "</p>";
echo "<p>Server: " . $_SERVER['SERVER_SOFTWARE'] . "</p>";

try {
    $pdo = new PDO("mysql:host={$host};dbname={$db};charset=utf8mb4", $user, $pass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
    echo "<p style='color:green;'>Database connected successfully!</p>";

    $stmt = $pdo->query("SELECT * FROM users");
    echo "<table border='1'><tr><th>ID</th><th>Username</th><th>Email</th></tr>";
    while ($row = $stmt->fetch()) {
        echo "<tr><td>{$row['id']}</td><td>" . htmlspecialchars($row['username']) .
             "</td><td>" . htmlspecialchars($row['email']) . "</td></tr>";
    }
    echo "</table>";
} catch (PDOException $e) {
    echo "<p style='color:red;'>Connection failed: " . htmlspecialchars($e->getMessage()) . "</p>";
}
?>
```

**Verify**: Open `http://localhost:8080` in a browser -- should display PHP version, server info, and the users table.

### 8. Start the stack

Build and launch all containers. [src4]

```bash
docker compose up -d --build
```

**Verify**: `docker compose ps` -- all three services should show `running` (healthy).

## Code Examples

### PHP PDO: Secure database connection with error handling

```php
<?php
// Input:  Environment variables DB_HOST, DB_NAME, DB_USER, DB_PASS
// Output: PDO connection object or error message

function createDbConnection(): PDO {
    $dsn = sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4',
        getenv('DB_HOST') ?: 'db',
        getenv('DB_NAME') ?: 'lamp_app'
    );
    return new PDO($dsn, getenv('DB_USER'), getenv('DB_PASS'), [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ]);
}
```

### Docker Compose: Production-hardened configuration

> Full script: [docker-compose-production-hardened-configuration.yml](scripts/docker-compose-production-hardened-configuration.yml) (26 lines)

```yaml
# Input:  .env file with credentials, app code in ./src
# Output: Production LAMP stack (no phpMyAdmin, no exposed DB port)
services:
  php-apache:
    build: ./php
# ... (see full script)
```

### Bash: Health check and restart script

```bash
#!/bin/bash
# Input:  Running Docker Compose LAMP stack
# Output: Status report + restart if unhealthy

cd /path/to/lamp-project

# Check if all services are running
if ! docker compose ps --status running | grep -q "lamp-php"; then
    echo "PHP-Apache is down, restarting..."
    docker compose restart php-apache
fi

if ! docker compose ps --status running | grep -q "lamp-mysql"; then
    echo "MySQL is down, restarting..."
    docker compose restart db
fi

echo "Stack status:"
docker compose ps
```

## Anti-Patterns

### Wrong: Using localhost as MySQL host in PHP containers

```php
// BAD -- 'localhost' refers to the container's own loopback, not the MySQL container
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
// Error: SQLSTATE[HY000] [2002] Connection refused
```

### Correct: Use the Docker Compose service name

```php
// GOOD -- 'db' is the service name in docker-compose.yml
$pdo = new PDO('mysql:host=db;dbname=mydb', 'user', 'pass');
// Docker's internal DNS resolves 'db' to the MySQL container's IP
```

### Wrong: Hardcoding credentials in docker-compose.yml

```yaml
# BAD -- credentials visible in version control
services:
  db:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: supersecret123
      MYSQL_PASSWORD: mypassword
```

### Correct: Use .env file or Docker secrets

```yaml
# GOOD -- credentials loaded from .env file (gitignored)
services:
  db:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
# .env file is auto-loaded by Docker Compose and should be in .gitignore
```

### Wrong: Using bind mount for MySQL data

```yaml
# BAD -- bind mount causes permission issues and is not portable
services:
  db:
    image: mysql:8.4
    volumes:
      - ./mysql_data:/var/lib/mysql
```

### Correct: Use named volumes for database data

```yaml
# GOOD -- named volumes are managed by Docker, portable and reliable
services:
  db:
    image: mysql:8.4
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:
```

### Wrong: No health check -- PHP starts before MySQL is ready

```yaml
# BAD -- depends_on without health check only waits for container start, not readiness
services:
  php-apache:
    depends_on:
      - db
  db:
    image: mysql:8.4
    # No healthcheck defined
```

### Correct: Use healthcheck with condition

```yaml
# GOOD -- PHP waits until MySQL passes health check
services:
  php-apache:
    depends_on:
      db:
        condition: service_healthy
  db:
    image: mysql:8.4
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
```

## Common Pitfalls

- **MySQL "Connection refused" on first start**: MySQL takes 10-30 seconds to initialize on first run. Fix: Add a `healthcheck` to the MySQL service and use `depends_on` with `condition: service_healthy`. [src2]
- **PHP extensions missing**: The base `php:apache` image has minimal extensions. Fix: Use `docker-php-ext-install pdo_mysql mysqli` in your Dockerfile -- check available extensions with `docker-php-ext-install --help`. [src1]
- **Apache mod_rewrite not working**: The default Apache config in the PHP image has `AllowOverride None`. Fix: Add `RUN a2enmod rewrite` in your Dockerfile and ensure your Apache config or `.htaccess` allows overrides. [src4]
- **Data loss on `docker compose down -v`**: The `-v` flag deletes named volumes including your database. Fix: Use `docker compose down` (without `-v`) for normal stops. Only use `-v` when you want to reset the database. [src2]
- **File permission issues with bind mounts**: Files created inside the container are owned by `www-data` (UID 33) but may not be writable from the host. Fix: Set `user: "${UID}:${GID}"` in compose or `chown` in Dockerfile. [src6]
- **MySQL init scripts not running**: Scripts in `/docker-entrypoint-initdb.d/` only run on first start when no database exists. Fix: Run `docker compose down -v` to delete the volume and force re-initialization. [src2]
- **OPcache serving stale PHP files in development**: OPcache caches compiled PHP, so file changes may not appear. Fix: Set `opcache.validate_timestamps=1` and `opcache.revalidate_freq=0` in `php.ini` for development. [src6]

## Diagnostic Commands

```bash
# Check all container status
docker compose ps

# View real-time logs for all services
docker compose logs -f

# View MySQL logs specifically
docker compose logs -f db

# Test MySQL connectivity from PHP container
docker compose exec php-apache php -r "new PDO('mysql:host=db;dbname=lamp_app', 'lamp_user', 'password');"

# Check installed PHP extensions
docker compose exec php-apache php -m

# Check PHP configuration values
docker compose exec php-apache php -i | grep -E "display_errors|memory_limit|upload_max"

# Check Apache modules
docker compose exec php-apache apache2ctl -M

# Connect to MySQL CLI
docker compose exec db mysql -u root -p

# Check MySQL process list
docker compose exec db mysqladmin -u root -p processlist

# Inspect network connectivity between containers
docker compose exec php-apache ping -c 3 db

# Check disk usage of named volumes
docker system df -v | grep mysql_data

# Rebuild without cache (when Dockerfile changes are not picked up)
docker compose build --no-cache php-apache
```

## Version History & Compatibility

| Component | Version | Status | Notes |
|---|---|---|---|
| PHP | 8.3.x | Current (Active) | Latest features, JIT improvements |
| PHP | 8.2.x | Active until Dec 2025 | Stable, widely used |
| PHP | 8.1.x | Security-only until Dec 2025 | DNF, enums, fibers |
| PHP | 8.0.x | EOL (Nov 2023) | Named arguments, union types -- upgrade |
| PHP | 7.4.x | EOL (Nov 2022) | Do not use for new projects |
| MySQL | 8.4 LTS | Current LTS | Long-term support, recommended for stability |
| MySQL | 8.0.x | GA until Apr 2026 | Widely deployed, upgrade to 8.4 recommended |
| MySQL | 9.x | Innovation | Cutting-edge features, shorter support cycle |
| Docker Compose | V2 | Current | `docker compose` (space) -- V1 `docker-compose` is deprecated |
| Docker Engine | 24.x-27.x | Current | Required for Compose V2 health check conditions |
| phpMyAdmin | 5.2.x | Current | Supports PHP 8.1-8.3, MySQL 5.5+ |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Local PHP/MySQL development environment | Production deployment at scale | Kubernetes + managed database (RDS, Cloud SQL) |
| Rapid prototyping of PHP web apps | You need Nginx instead of Apache | Docker LEMP stack (Nginx + PHP-FPM + MySQL) |
| CI/CD pipeline testing with real MySQL | You need PostgreSQL instead of MySQL | Docker Compose with postgres image |
| Team onboarding (consistent dev environment) | Your app is Node.js/Python/Go (not PHP) | Framework-specific Docker setup |
| Legacy PHP app containerization | You need high-availability clustering | Docker Swarm or Kubernetes with replicas |

## Important Caveats

- Docker Compose V1 (`docker-compose` with hyphen) is deprecated -- use V2 (`docker compose` with space); the `depends_on.condition` syntax requires V2
- MySQL 8.4+ uses `caching_sha2_password` as default authentication plugin -- older PHP MySQL clients may need `mysql_native_password` (set via `--default-authentication-plugin` or `mysql.cnf`)
- On macOS and Windows (Docker Desktop), bind mount performance is significantly slower than on Linux -- use named volumes or Docker's `cached`/`delegated` mount options for better performance
- The `php:8.3-apache` image is based on Debian Bookworm (~400MB) -- for smaller images, consider `php:8.3-alpine` variants (but these require different package managers and may have compatibility issues)
- MySQL initialization scripts in `/docker-entrypoint-initdb.d/` run in alphabetical order -- prefix files with numbers (01-, 02-) to control execution order
- Docker Desktop on Windows requires WSL2 backend for best performance -- Hyper-V backend is significantly slower for file-intensive PHP workloads

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Docker LEMP Stack (Nginx + MySQL + PHP)](/software/devops/docker-lemp-stack/2026)
- [Docker WordPress Setup](/software/devops/docker-wordpress/2026)
