Using Docker for Laravel Development
One of the biggest pain points in web development is environment inconsistency. What works on your machine breaks on your teammate's, and somehow everything falls apart in production. Docker solves this.
This guide walks through setting up a Laravel development environment with Docker, covering the essential services you'll need day-to-day.
Why Docker for Laravel?
Traditional Laravel development requires PHP, Composer, MySQL, Redis, and often more services installed directly on your machine. Each project might need different PHP versions or extensions, leading to conflicts.
With Docker, you get:
- Isolated environments per project
- Identical stacks across your team
- Quick onboarding — new devs just run
docker compose up - Production parity — your dev stack matches staging and production
Project Structure
laravel-app/
├── docker/
│ ├── php/
│ │ └── Dockerfile
│ └── nginx/
│ └── default.conf
├── docker-compose.yml
├── src/ # Laravel source
└── .env
Docker Compose Setup
Here's a solid docker-compose.yml for Laravel:
services:
app:
build:
context: .
dockerfile: docker/php/Dockerfile
container_name: laravel-app
restart: unless-stopped
working_dir: /var/www
volumes:
- ./src:/var/www
- ./docker/php/php.ini:/usr/local/etc/php/conf.d/local.ini
networks:
- laravel
webserver:
image: nginx:alpine
container_name: laravel-webserver
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./src:/var/www
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
networks:
- laravel
mysql:
image: mysql:8.0
container_name: laravel-mysql
restart: unless-stopped
environment:
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_USER: ${DB_USERNAME}
ports:
- "3307:3306"
volumes:
- dbdata:/var/lib/mysql
networks:
- laravel
redis:
image: redis:alpine
container_name: laravel-redis
restart: unless-stopped
ports:
- "6380:6379"
networks:
- laravel
volumes:
dbdata:
networks:
laravel:
driver: bridge
PHP Dockerfile
Multi-stage builds keep the image lean:
FROM php:8.3-fpm as base
RUN apt-get update && apt-get install -y \
git curl libpng-dev libonig-dev libxml2-dev zip unzip \
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www
Nginx Configuration
server {
listen 80;
server_name localhost;
root /var/www/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Environment Configuration
Your .env file for the Docker setup:
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret
REDIS_HOST=redis
REDIS_PORT=6379
Running the Stack
# Start containers
docker compose up -d
# Run Artisan commands inside the container
docker compose exec app php artisan migrate
docker compose exec app php artisan queue:work
# View logs
docker compose logs -f app
# Stop everything
docker compose down
Common Commands Cheatsheet
| Task | Command |
|---|---|
| Install Composer deps | docker compose exec app composer install |
| Run migrations | docker compose exec app php artisan migrate |
| Tinker | docker compose exec app php artisan tinker |
| Clear cache | docker compose exec app php artisan optimize:clear |
| NPM build | docker compose exec app npm run build |
| MySQL shell | docker compose exec mysql mysql -u laravel -p laravel |
Tips for Production
When moving to production, consider:
- Use a non-root user in your Dockerfile for security
- Implement health checks for each service
- Use environment-specific compose files —
docker-compose.ymlfor dev,docker-compose.prod.ymlfor prod - Set up a reverse proxy (Traefik, Caddy, or Nginx Proxy Manager)
- Use Docker secrets instead of plain
.envfor sensitive data
Conclusion
Docker eliminates the "it works on my machine" problem. For Laravel developers, containerization means you spend less time debugging environment issues and more time building features. Start with the setup above, customize it for your stack, and enjoy consistent development across your team.
Muhammad Fauzun Naja
Backend Developer sharing insights about Laravel, PHP, and DevOps.