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.docker compose up -d --buildlocalhost as the MySQL host inside PHP containers -- you must use the Docker Compose service name (e.g., db) instead.MYSQL_ALLOW_EMPTY_PASSWORD=yes in production -- always set a strong root password or use Docker secretsdocker-compose.yml for production -- use .env files or Docker secrets (_FILE suffix)0.0.0.0 in production -- bind to 127.0.0.1 or remove the port mapping entirelydb) as the MySQL hostname in PHP -- never use localhost or 127.0.0.1 from within containers| Service | Image | Ports | Volumes | Key Env |
|---|---|---|---|---|
| php-apache | php:8.3-apache | 8080:80 | ./src:/var/www/html | DB_HOST=db |
| db (MySQL) | mysql:8.4 | 127.0.0.1:3306:3306 (dev) | 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 |
| 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 |
| 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 |
| 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 |
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
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.
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.
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.
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.
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.
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;"
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.
<?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,
]);
}
# 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
#!/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
// 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
// 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
# BAD -- credentials visible in version control
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: supersecret123
# GOOD -- credentials loaded from .env file (gitignored)
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
# BAD -- bind mount causes permission issues and is not portable
services:
db:
volumes:
- ./mysql_data:/var/lib/mysql
# GOOD -- named volumes are managed by Docker, portable and reliable
services:
db:
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
# BAD -- depends_on without health check only waits for container start
services:
php-apache:
depends_on:
- db
# 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
healthcheck and use depends_on with condition: service_healthy. [src2]php:apache image has minimal extensions. Fix: Use docker-php-ext-install pdo_mysql mysqli in your Dockerfile. [src1]AllowOverride None. Fix: Add RUN a2enmod rewrite in Dockerfile and configure .htaccess. [src4]docker compose down -v: The -v flag deletes named volumes. Fix: Use docker compose down without -v for normal stops. [src2]www-data (UID 33). Fix: Set user: "${UID}:${GID}" in compose or use chown in Dockerfile. [src6]/docker-entrypoint-initdb.d/ only run on first start. Fix: Run docker compose down -v to force re-initialization. [src2]opcache.validate_timestamps=1 and opcache.revalidate_freq=0 for development. [src6]# 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
| 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 | Fibers, enums |
| PHP | 8.0.x | EOL (Nov 2023) | Upgrade recommended |
| MySQL | 8.4 LTS | Current LTS | Recommended for stability |
| MySQL | 8.0.x | GA until Apr 2026 | Upgrade to 8.4 recommended |
| MySQL | 9.x | Innovation | Shorter support cycle |
| Docker Compose | V2 | Current | V1 (docker-compose) is deprecated |
| phpMyAdmin | 5.2.x | Current | Supports PHP 8.1-8.3, MySQL 5.5+ |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Local PHP/MySQL development environment | Production deployment at scale | Kubernetes + managed database |
| Rapid prototyping of PHP web apps | You need Nginx instead of Apache | Docker LEMP stack |
| CI/CD pipeline testing with real MySQL | You need PostgreSQL | Docker Compose with postgres image |
| Team onboarding (consistent dev environment) | Your app is not PHP | Framework-specific Docker setup |
| Legacy PHP app containerization | High-availability clustering needed | Docker Swarm or Kubernetes |
docker-compose with hyphen) is deprecated -- use V2 (docker compose with space); the depends_on.condition syntax requires V2caching_sha2_password as default auth plugin -- older PHP MySQL clients may need mysql_native_passwordcached/delegated mount optionsphp:8.3-apache image is ~400MB (Debian Bookworm) -- for smaller images, consider php:8.3-alpine (but requires different package managers)/docker-entrypoint-initdb.d/ run alphabetically -- prefix files with numbers (01-, 02-) to control order