AboutBlogContact
DevOpsMarch 8, 2016 4 min read 25

How to Use Docker for PHP Development: Replacing WAMP/MAMP in 2016

AunimedaAunimeda
📋 Table of Contents

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.

Read Also

Cloud Hosting Comparison 2026: AWS vs GCP vs Azure vs Hetzner vs Vercelaunimeda
DevOps

Cloud Hosting Comparison 2026: AWS vs GCP vs Azure vs Hetzner vs Vercel

Which cloud provider to choose for your startup in 2026. Real pricing comparison, performance benchmarks, and the hosting stack that makes sense at each stage.

DevOps for Startups - What You Actually Need (And What to Skip)aunimeda
DevOps

DevOps for Startups - What You Actually Need (And What to Skip)

Most startup DevOps guides tell you to set up Kubernetes. You don't need Kubernetes. Here's the minimal, effective DevOps setup for a product with under 100k users.

Docker and CI/CD for a Small Dev Team: What We Actually Ship in Productionaunimeda
DevOps

Docker and CI/CD for a Small Dev Team: What We Actually Ship in Production

Not every team needs Kubernetes. Here's the Docker-based CI/CD setup we run for 6 production projects with a team of 8 - GitHub Actions, Docker Compose, Nginx, and zero Kubernetes.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Free consultation.

Get Consultation All articles