“It works perfectly on my machine.” These six words have caused more client crises, delayed more launches, and cost more money than almost any other problem in software development. Docker eliminates this entirely. Here’s how to use it as a PHP developer — even if you’ve never touched a container before.
The Problem Docker Solves
Without Docker, every developer on your team has a slightly different environment: different PHP version, different MySQL version, different extensions installed. Your local machine runs PHP 8.1, the client’s server runs PHP 7.4. Your colleague is on Windows, you’re on Mac. The staging server has a different timezone. Docker packages your entire environment — PHP version, extensions, web server, database — into a container that runs identically everywhere. Your local machine, staging server, and production server all run the exact same configuration.
Key Concepts in 2 Minutes
- Image: A blueprint for a container (like a PHP class before instantiation)
- Container: A running instance of an image (the actual running object)
- docker-compose.yml: A file that defines all your services (PHP, MySQL, Nginx) and how they connect
- Volume: A way to share files between your local machine and the container so edits appear instantly
Step 1: Install Docker Desktop
Download Docker Desktop from docker.com for Mac or Windows. On Ubuntu Linux:
sudo apt-get update
sudo apt-get install docker.io docker-compose
sudo usermod -aG docker $USER
# Log out and back in after this
Verify installation: docker --version and docker-compose --version
Step 2: Create Your Project Structure
my-project/
├── docker-compose.yml
├── docker/
│ ├── php/
│ │ └── Dockerfile
│ └── nginx/
│ └── default.conf
├── src/ ← your PHP code lives here
└── mysql/ ← database data (auto-created)
Step 3: The docker-compose.yml File
Create docker-compose.yml in your project root:
version: '3.8'
services:
# PHP-FPM Service
php:
build:
context: ./docker/php
dockerfile: Dockerfile
container_name: php_app
volumes:
- ./src:/var/www/html
depends_on:
- mysql
# Nginx Web Server
nginx:
image: nginx:alpine
container_name: nginx_server
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
# MySQL Database
mysql:
image: mysql:8.0
container_name: mysql_db
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: myapp
MYSQL_USER: appuser
MYSQL_PASSWORD: apppassword
ports:
- "3306:3306"
volumes:
- ./mysql:/var/lib/mysql
# phpMyAdmin (optional but handy)
phpmyadmin:
image: phpmyadmin:latest
container_name: phpmyadmin
ports:
- "8081:80"
environment:
PMA_HOST: mysql
PMA_USER: appuser
PMA_PASSWORD: apppassword
Step 4: Your PHP Dockerfile
Create docker/php/Dockerfile:
FROM php:8.2-fpm
# Install common extensions
RUN apt-get update && apt-get install -y \
libpng-dev \
libonig-dev \
libzip-dev \
zip \
unzip
RUN docker-php-ext-install \
pdo_mysql \
mbstring \
gd \
zip \
opcache
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
Step 5: Nginx Configuration
Create docker/nginx/default.conf:
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
Step 6: Start Your Environment
# Build and start all services (first time)
docker-compose up --build
# Start in background after the first time
docker-compose up -d
# Stop everything
docker-compose down
# View logs
docker-compose logs -f php
# Run a command inside the PHP container
docker-compose exec php bash
docker-compose exec php composer install
Visit http://localhost:8080 — your PHP site is running. phpMyAdmin is at http://localhost:8081.
WordPress in Docker
For WordPress, simplify even further with the official image:
version: '3.8'
services:
wordpress:
image: wordpress:php8.2-apache
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppassword
WORDPRESS_DB_NAME: wordpress
volumes:
- ./wp-content:/var/www/html/wp-content
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppassword
MYSQL_ROOT_PASSWORD: rootpassword
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
Practical Tips for Teams
- Add docker-compose.yml to Git — every team member gets the same environment with one command
- Add mysql/ to .gitignore — never commit database files
- Use .env files for passwords — reference them in docker-compose.yml with
${MYSQL_PASSWORD} - Use named volumes for databases — data persists even when containers are stopped
Moving to Production
Docker for development doesn’t automatically mean Docker in production — many teams develop with Docker but deploy to a standard cPanel/VPS. That’s perfectly fine. The consistency benefit is still enormous. If you do want Docker in production, look at Docker Swarm (simpler) or Kubernetes (powerful, complex) for orchestrating multiple containers across servers. — We help development teams set up modern DevOps workflows. If your team is spending too much time on environment issues and manual deployments, talk to us →
Leave a comment