How to Use Docker for PHP Development: Replacing WAMP/MAMP in 2016
Short answer: Create docker-compose.yml with three services: nginx (port 80), php (PHP-FPM 7.0), mysql (5.7). Mount your project directory as a volume. Run docker-compose up -d. All team members get identical environments regardless of their host OS.
docker-compose.yml
# docker-compose.yml — PHP 7.0 + nginx + MySQL 5.7
# Docker Compose file format version 2 (stable in 2016)
version: '2'
services:
nginx:
image: nginx:1.10-alpine
ports:
- "80:80"
- "443:443"
volumes:
- .:/var/www/html # Project code
- ./docker/nginx/vhost.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
networks:
- app-network
php:
build: ./docker/php # Custom Dockerfile
volumes:
- .:/var/www/html
environment:
APP_ENV: local
DB_HOST: mysql
DB_DATABASE: myapp
DB_USERNAME: myapp_user
DB_PASSWORD: secret
depends_on:
- mysql
networks:
- app-network
mysql:
image: mysql:5.7
ports:
- "3306:3306" # Expose for GUI tools (TablePlus, Sequel Pro)
environment:
MYSQL_ROOT_PASSWORD: rootsecret
MYSQL_DATABASE: myapp
MYSQL_USER: myapp_user
MYSQL_PASSWORD: secret
volumes:
- mysql-data:/var/lib/mysql # Named volume = data persists between restarts
networks:
- app-network
redis:
image: redis:3.2-alpine
ports:
- "6379:6379"
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
mysql-data:
driver: local
docker/php/Dockerfile
# docker/php/Dockerfile
FROM php:7.0-fpm
# Install PHP extensions
RUN docker-php-ext-install pdo pdo_mysql mbstring opcache
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Install development tools
RUN apt-get update && apt-get install -y \
git \
unzip \
&& rm -rf /var/lib/apt/lists/*
# PHP configuration
COPY php.ini /usr/local/etc/php/conf.d/custom.ini
WORKDIR /var/www/html
docker/php/php.ini
; docker/php/php.ini
[PHP]
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
error_log = /dev/stderr
; Performance (development settings)
memory_limit = 256M
max_execution_time = 120
upload_max_filesize = 20M
post_max_size = 20M
; OPcache
opcache.enable = 1
opcache.revalidate_freq = 0 ; In development: check for file changes on every request
opcache.validate_timestamps = 1
; Timezone
date.timezone = Asia/Bishkek
docker/nginx/vhost.conf
# docker/nginx/vhost.conf
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php;
# Laravel / Symfony / CodeIgniter front controller
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000; # Service name from docker-compose
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Static files: don't pass to PHP
location ~* \.(css|js|jpg|jpeg|png|gif|ico|woff|woff2)$ {
expires 30d;
access_log off;
}
# Security: block access to hidden files
location ~ /\. {
deny all;
}
}
Commands
# Start all containers
docker-compose up -d
# Stop
docker-compose down
# Stop and remove volumes (DELETES database data)
docker-compose down -v
# View logs
docker-compose logs -f
docker-compose logs -f php # Just PHP logs
docker-compose logs --tail=100 # Last 100 lines
# Run commands inside PHP container
docker-compose exec php bash
docker-compose exec php composer install
docker-compose exec php php artisan migrate
docker-compose exec php php artisan tinker
# MySQL shell
docker-compose exec mysql mysql -u myapp_user -psecret myapp
# Rebuild PHP container after Dockerfile changes
docker-compose build php
docker-compose up -d --no-deps php
The Volume Mount Performance Problem (2016 Mac/Windows)
# PROBLEM: On Mac and Windows, volume mounts were SLOW in 2016
# Docker used osxfs/VBoxFS — up to 10x slower than Linux native filesystem
# A Laravel artisan command taking 0.3s on Linux took 3s on Mac
# Partial solutions in 2016:
# Solution 1: Exclude vendor/ and node_modules/ from sync
# Use named volumes for these (inside container, not synced)
services:
php:
volumes:
- .:/var/www/html # Sync project
- vendor:/var/www/html/vendor # Don't sync vendor/
- node_modules:/var/www/html/node_modules # Don't sync node_modules/
volumes:
vendor:
node_modules:
# Then copy vendor from container after composer install:
# docker-compose exec php composer install
# The vendor/ is now in the named volume, not synced to host
# Solution 2: docker-sync (tool for faster macOS volume mounting)
# gem install docker-sync
# docker-sync.yml:
# sync:
# app-sync:
# src: '.'
# sync_strategy: 'unison'
# This was the community fix in 2016 — native fixes came later
.dockerignore
# .dockerignore — files not sent to Docker build context
.git
node_modules
vendor
*.log
.env
.env.local
docker/
tests/
Production vs Development
# docker-compose.prod.yml (override for production)
version: '2'
services:
php:
environment:
APP_ENV: production
APP_DEBUG: 'false'
volumes:
- .:/var/www/html # In prod, consider copying files INTO image instead
# Development:
docker-compose up -d
# Production:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
The productivity gain from Docker in 2016 was immediate: onboarding a new developer went from "set up MAMP, install PHP extensions, configure MySQL, debug permission issues" (half a day) to git clone && docker-compose up -d (15 minutes). That alone justified the learning curve.