Docker Compose LAMP Stack: Apache, MySQL, PHP Reference

Type: Software Reference Confidence: 0.93 Sources: 7 Verified: 2026-02-27 Freshness: 2026-02-27

TL;DR

Constraints

Quick Reference

Service Configuration Summary

ServiceImagePortsVolumesKey Env
php-apachephp:8.3-apache8080:80./src:/var/www/htmlDB_HOST=db
db (MySQL)mysql:8.4127.0.0.1:3306:3306 (dev)mysql_data:/var/lib/mysqlMYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD
phpmyadminphpmyadmin:latest8081:80--PMA_HOST=db, PMA_PORT=3306

PHP Extension Install Helpers

Helper ScriptPurposeExample
docker-php-ext-installInstall bundled PHP extensionsdocker-php-ext-install pdo_mysql mysqli
docker-php-ext-configureConfigure extension before installingdocker-php-ext-configure gd --with-jpeg
docker-php-ext-enableEnable a PECL-installed extensiondocker-php-ext-enable redis

MySQL Initialization

MethodMount PathSupported 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

ActionCommand
Start stackdocker compose up -d --build
Stop stackdocker compose down
Stop + delete volumesdocker compose down -v
View logsdocker compose logs -f [service]
Shell into containerdocker compose exec php-apache bash
Rebuild single servicedocker 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, Docker secrets, no phpMyAdmin
│   └── 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]

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

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]

# php/Dockerfile
FROM php:8.3-apache

RUN apt-get update && apt-get install -y \
    libpng-dev libjpeg-dev libfreetype6-dev libzip-dev unzip \
    && rm -rf /var/lib/apt/lists/*

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

RUN a2enmod rewrite headers expires

COPY php.ini /usr/local/etc/php/conf.d/custom.ini
WORKDIR /var/www/html
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. [src6]

; php/php.ini
display_errors = On
error_reporting = E_ALL
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
opcache.enable = 1
opcache.validate_timestamps = 1
opcache.revalidate_freq = 0

Verify: 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]

services:
  php-apache:
    build: ./php
    ports: ["8080:80"]
    volumes: ["./src:/var/www/html"]
    depends_on:
      db: { condition: service_healthy }
    environment:
      DB_HOST: db
      DB_NAME: ${MYSQL_DATABASE}
    networks: [lamp-network]

  db:
    image: mysql:8.4
    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"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks: [lamp-network]

  phpmyadmin:
    image: phpmyadmin:latest
    ports: ["8081:80"]
    environment:
      PMA_HOST: db
      PMA_PORT: 3306
    depends_on:
      db: { condition: service_healthy }
    networks: [lamp-network]

networks:
  lamp-network:

volumes:
  mysql_data:

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

5. Create the .env file

Store credentials outside the compose file. [src7]

# .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 initialization SQL and test PHP

Pre-populate the database and verify connectivity. [src2]

-- mysql/init/01-create-tables.sql
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;

Verify: docker compose exec db mysql -u lamp_user -p lamp_app -e "SHOW TABLES;"

7. Start the stack

Build and launch all containers. [src4]

docker compose up -d --build

Verify: docker compose ps -- all services should show running (healthy). Visit http://localhost:8080.

Code Examples

PHP PDO: Secure database connection

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

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

# No phpMyAdmin, no exposed DB port, Docker secrets for passwords
services:
  php-apache:
    build: ./php
    ports: ["80:80", "443:443"]
    volumes: ["app_code:/var/www/html:ro"]
    restart: always

  db:
    image: mysql:8.4
    volumes: ["mysql_data:/var/lib/mysql"]
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
    secrets: [db_root_password]
    restart: always

secrets:
  db_root_password:
    file: ./secrets/db_root_password.txt

Bash: Health check and restart script

#!/bin/bash
# Input:  Running Docker Compose LAMP stack
# Output: Status report + restart if unhealthy
cd /path/to/lamp-project

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

echo "Stack status:"
docker compose ps

Anti-Patterns

Wrong: Using localhost as MySQL host in PHP containers

// 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

// 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

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

Correct: Use .env file or Docker secrets

# GOOD -- credentials loaded from .env file (gitignored)
services:
  db:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}

Wrong: Using bind mount for MySQL data

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

Correct: Use named volumes for database data

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

volumes:
  mysql_data:

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

# BAD -- depends_on without health check only waits for container start
services:
  php-apache:
    depends_on:
      - db

Correct: Use healthcheck with condition

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

Common Pitfalls

Diagnostic Commands

# Check all container status
docker compose ps

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

# 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 Apache modules
docker compose exec php-apache apache2ctl -M

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

# Inspect network 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
docker compose build --no-cache php-apache

Version History & Compatibility

ComponentVersionStatusNotes
PHP8.3.xCurrent (Active)Latest features, JIT improvements
PHP8.2.xActive until Dec 2025Stable, widely used
PHP8.1.xSecurity-only until Dec 2025Fibers, enums
PHP8.0.xEOL (Nov 2023)Upgrade recommended
MySQL8.4 LTSCurrent LTSRecommended for stability
MySQL8.0.xGA until Apr 2026Upgrade to 8.4 recommended
MySQL9.xInnovationShorter support cycle
Docker ComposeV2CurrentV1 (docker-compose) is deprecated
phpMyAdmin5.2.xCurrentSupports PHP 8.1-8.3, MySQL 5.5+

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Local PHP/MySQL development environmentProduction deployment at scaleKubernetes + managed database
Rapid prototyping of PHP web appsYou need Nginx instead of ApacheDocker LEMP stack
CI/CD pipeline testing with real MySQLYou need PostgreSQLDocker Compose with postgres image
Team onboarding (consistent dev environment)Your app is not PHPFramework-specific Docker setup
Legacy PHP app containerizationHigh-availability clustering neededDocker Swarm or Kubernetes

Important Caveats

Related Units