"It works on my machine." In a development team, this phrase is almost a joke. Almost, because it causes real disasters. The app works perfectly locally, then goes to production and refuses to start. PHP 7.4 locally, PHP 8.2 in prod. MySQL 5.7 locally, MySQL 8.0 in prod. This kind of inconsistency has cost me entire nights of debugging.
Docker eliminates this problem. Here's how to dockerize a Laravel application properly.
Understanding Docker in 2 Minutes
Docker is not a virtual machine. It's a system of lightweight containers that package your application with exactly the environment it needs: the right PHP version, the right extensions, the right environment variables. Nothing more, nothing less.
- Image — an immutable template (e.g.: PHP 8.3 + Laravel extensions)
- Container — a running instance of an image
- docker-compose — orchestration of multiple containers (PHP + MySQL + Redis)
- Volume — data persistence outside the container
The Dockerfile for Laravel
FROM php:8.3-fpm-alpine
RUN apk add --no-cache \
libpng-dev \
libzip-dev \
oniguruma-dev \
&& docker-php-ext-install \
pdo_mysql \
mbstring \
zip \
gd \
opcache
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts
COPY . .
RUN chown -R www-data:www-data storage bootstrap/cache \
&& chmod -R 775 storage bootstrap/cache
EXPOSE 9000
CMD ["php-fpm"]
The docker-compose.yml: Orchestrating the Full Environment
version: '3.8'
services:
app:
build: .
container_name: laravel_app
volumes:
- .:/var/www/html
networks:
- laravel
depends_on:
- db
- redis
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- .:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
networks:
- laravel
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/mysql
networks:
- laravel
redis:
image: redis:alpine
networks:
- laravel
networks:
laravel:
driver: bridge
volumes:
db_data:
Starting the Project in One Command
# Launch all containers in the background
docker-compose up -d
# Install Laravel dependencies
docker-compose exec app composer install
# Configure the application
docker-compose exec app php artisan key:generate
docker-compose exec app php artisan migrate --seed
Concrete Benefits for Your Workflow
- ✅ 5-minute onboarding — a new dev clones the repo, runs
docker-compose up, it's ready - ✅ Identical environments — dev, staging, production all use the same Dockerfile
- ✅ Full isolation — each project has its own PHP and MySQL versions
- ✅ Simplified CI/CD — your GitHub Actions pipeline uses the same container
Conclusion
Adopting Docker for your Laravel projects is no longer optional if you work in a team or deploy regularly. The initial investment (one to two hours to configure everything) pays off immediately when you avoid your first environment conflict.
And trust me, someday you'll inevitably avoid that all-nighter wondering why it "worked on your machine."