Docker for PHP and Laravel Developers — Dockerfile and Compose from Scratch
A practical guide to writing Dockerfiles and docker-compose.yml files for PHP and Laravel. Multi-stage builds, MySQL, Redis, and production-ready patterns.
Docker solves a problem every developer has hit: "it works on my machine." By packaging your application and its dependencies into a container, Docker ensures the environment is identical from development to staging to production. For PHP and Laravel developers who have historically deployed to shared hosting or bare VPS servers, Docker is a significant shift — but a worthwhile one.
This guide covers writing Dockerfiles and Compose files for PHP and Laravel specifically, with practical patterns you can use immediately.
What Docker actually does
A Docker container is a lightweight, isolated process that includes everything needed to run your application — the runtime, dependencies, configuration, and code. Unlike a virtual machine, containers share the host operating system kernel, making them fast to start and cheap to run.
A Dockerfile defines how to build a container image. A docker-compose.yml file defines how to run multiple containers together — your application, database, cache, and any other services — as a single unit.
A Dockerfile for PHP-FPM
PHP applications typically run with PHP-FPM (FastCGI Process Manager) behind a web server. A production-ready Dockerfile for a PHP application:
FROM php:8.3-fpm-alpine
RUN apk add --no-cache \
libpng-dev libjpeg-turbo-dev libwebp-dev libzip-dev \
zip unzip curl \
&& docker-php-ext-install pdo pdo_mysql zip gd opcache
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY composer*.json ./
RUN composer install --no-dev --optimize-autoloader --no-interaction
COPY . .
RUN chown -R www-data:www-data /var/www/html
USER www-data
EXPOSE 9000
CMD ["php-fpm"]
This uses the Alpine variant for a smaller image. It installs only the PHP extensions the application needs. Composer runs before copying application code so that the dependency layer is cached — if your code changes but composer.json does not, Docker reuses the cached layer and the build is much faster.
A Dockerfile for Laravel with multi-stage builds
Multi-stage builds produce smaller final images by separating the build environment from the runtime environment. The first stage installs dependencies and compiles assets. The second stage copies only what is needed to run.
# Stage 1: Install Composer dependencies
FROM composer:latest AS vendor
WORKDIR /app
COPY composer*.json ./
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Stage 2: Production image
FROM php:8.3-fpm-alpine
RUN apk add --no-cache \
libpng-dev libjpeg-turbo-dev libwebp-dev libzip-dev \
zip unzip curl git supervisor \
&& docker-php-ext-install pdo pdo_mysql zip gd opcache pcntl bcmath
WORKDIR /var/www/html
COPY --from=vendor /app/vendor ./vendor
COPY . .
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html/storage \
&& php artisan config:cache \
&& php artisan route:cache \
&& php artisan view:cache
USER www-data
EXPOSE 9000
CMD ["php-fpm"]
The php artisan config:cache, route:cache, and view:cache commands in the build step mean the application starts with pre-compiled configuration and routes — significantly faster than bootstrapping from scratch on every request.
Docker Compose for a full Laravel stack
A docker-compose.yml that brings up a Laravel application with MySQL and Redis:
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: myapp
restart: unless-stopped
ports:
- "9000:9000"
environment:
DB_HOST: mysql
DB_DATABASE: myapp
DB_USERNAME: myapp_user
DB_PASSWORD: secret
REDIS_HOST: redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- myapp_net
mysql:
image: mysql:8.0
container_name: myapp_mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
MYSQL_USER: myapp_user
MYSQL_PASSWORD: secret
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
networks:
- myapp_net
redis:
image: redis:7-alpine
container_name: myapp_redis
restart: unless-stopped
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- myapp_net
volumes:
mysql_data:
redis_data:
networks:
myapp_net:
driver: bridge
The depends_on with condition: service_healthy ensures the application container only starts after MySQL and Redis are actually ready to accept connections — not just started. This prevents the common race condition where the app starts before the database is ready.
Rather than writing these files by hand, the Dockerfile and Docker Compose Generator builds both files through a wizard. Select your stack (Node.js, PHP-FPM, Laravel, Python, Django, Go, or static Nginx), configure the app name, port, and version, toggle multi-stage builds and non-root user, add services from a checklist, and download both files. Eight stack presets cover the most common configurations.
A .gitignore for Docker projects
Docker projects accumulate files that should not be committed: .env files with secrets, local override compose files, and volume data. The Gitignore Generator covers Docker, your application stack, and your IDE in one step — select Docker, PHP, Laravel, and your editor from the technology list and download the combined .gitignore.
Essential Docker commands
# Build and start all services in the background
docker compose up -d --build
# View logs from all services
docker compose logs -f
# Run artisan commands inside the app container
docker compose exec app php artisan migrate
# Stop all services
docker compose down
# Stop and remove volumes (resets database)
docker compose down -v
Tools
- Dockerfile & Docker Compose Generator — generate production-ready Docker files for any stack
- Server Config Generator — generate Nginx or Apache configs to front your Docker containers
- Gitignore Generator — create a .gitignore covering Docker, PHP, Laravel, and your IDE
- .htaccess Generator — if you are running Apache inside your container