If you have ever heard "it works on my machine" from a teammate, Docker is the cure. It packages your entire development environment - PHP, MySQL, Redis, Nginx - into containers that run identically everywhere.
This guide is written for PHP developers who have never used Docker. By the end, you will have a working Laravel or CodeIgniter project running in containers with hot reloading, database persistence, and a setup that mirrors production.
Why Docker for PHP?
Traditional PHP setups (XAMPP, WAMP, MAMP, Laragon) have real problems:
- Everyone on the team has a slightly different PHP version or extension set
- Switching between PHP 8.1 and 8.3 for different projects is painful
- MySQL version mismatches cause migration failures
- Setting up the same environment on a new developer laptop takes hours
- Your local environment never matches production
Docker solves all of these. One docker compose up command and every developer has an identical environment in minutes.
Installing Docker
Install Docker Desktop from docker.com. It works on Windows, macOS, and Linux. On Windows, make sure WSL 2 is enabled for best performance.
Verify the installation:
docker --version
docker compose version
Project Structure
Add these files to your PHP project root:
your-project/
docker/
nginx/
default.conf
php/
Dockerfile
docker-compose.yml
.dockerignore
Step 1: PHP Dockerfile
Create docker/php/Dockerfile:
FROM php:8.3-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git curl zip unzip libpng-dev libonig-dev \
libxml2-dev libzip-dev libfreetype6-dev \
libjpeg62-turbo-dev libwebp-dev \
&& docker-php-ext-configure gd \
--with-freetype --with-jpeg --with-webp \
&& docker-php-ext-install \
pdo_mysql mbstring exif pcntl bcmath gd zip \
&& pecl install redis && docker-php-ext-enable redis \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www/html
# Copy project files
COPY . .
# Install dependencies
RUN composer install --no-dev --optimize-autoloader
EXPOSE 9000
CMD ["php-fpm"]
This gives you PHP 8.3 with all the extensions Laravel and CodeIgniter need: PDO MySQL, GD for images, Redis, ZIP, and Composer.
Step 2: 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 $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
Step 3: Docker Compose
Create docker-compose.yml in your project root:
version: "3.8"
services:
php:
build:
context: .
dockerfile: docker/php/Dockerfile
volumes:
- .:/var/www/html
depends_on:
- mysql
- redis
networks:
- app-network
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- .:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
networks:
- app-network
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: app_db
MYSQL_USER: app_user
MYSQL_PASSWORD: app_password
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- app-network
redis:
image: redis:alpine
ports:
- "6379:6379"
networks:
- app-network
volumes:
mysql-data:
networks:
app-network:
Step 4: Start Everything
# Build and start all containers
docker compose up -d
# Check running containers
docker compose ps
# View logs
docker compose logs -f php
Visit http://localhost:8080 and your PHP app is running.
Essential Docker Commands for PHP Developers
# Run artisan commands (Laravel)
docker compose exec php php artisan migrate
docker compose exec php php artisan tinker
# Run Composer
docker compose exec php composer install
docker compose exec php composer require package-name
# Access MySQL CLI
docker compose exec mysql mysql -u app_user -p app_db
# Rebuild after Dockerfile changes
docker compose up -d --build
# Stop everything
docker compose down
# Stop and remove all data (fresh start)
docker compose down -v
Hot Reloading in Development
The volumes directive in docker-compose.yml mounts your local code into the container. When you edit a PHP file on your machine, the change is immediately reflected in the container. No restart needed - this is how hot reloading works with Docker.
For frontend assets (if running npm/Vite), add a Node container:
node:
image: node:20-alpine
volumes:
- .:/var/www/html
working_dir: /var/www/html
command: npm run dev
ports:
- "5173:5173"
networks:
- app-network
Environment-Specific Configuration
Update your Laravel .env to use Docker service names:
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=app_db
DB_USERNAME=app_user
DB_PASSWORD=app_password
REDIS_HOST=redis
REDIS_PORT=6379
CACHE_DRIVER=redis
SESSION_DRIVER=redis
Docker automatically resolves service names to container IPs within the same network. So mysql in your env file points to the MySQL container.
Common Issues and Fixes
Permission Errors on Linux
Add your user ID to the Dockerfile: RUN usermod -u 1000 www-data. This ensures file permissions match between your host and the container.
MySQL Connection Refused
MySQL takes a few seconds to initialize. Use depends_on with a health check, or simply wait 10 seconds after docker compose up before running migrations.
Slow Performance on Windows
Use WSL 2 backend and keep your project files inside the WSL filesystem (not on /mnt/c/). File operations through the Windows filesystem are significantly slower.
From Development to Production
The beauty of Docker is that the same containers run in production. The main differences:
- Do not mount volumes - copy files into the image instead
- Use
composer install --no-devto exclude dev dependencies - Add health checks to your compose file
- Use Docker secrets or environment variables for credentials
- Add a reverse proxy (Traefik or Nginx) for SSL termination
Summary
Docker eliminates the "works on my machine" problem for PHP teams. With a Dockerfile, Nginx config, and docker-compose.yml, every developer gets an identical environment in one command. Start with the basic setup in this guide, then add services as your project needs them. Once you experience the reliability of containerized development, you will never go back to XAMPP.