All posts
4 min read

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 PHP Laravel DevOps Developer Tools
Manoj Kumar
Manoj Kumar
Senior full-stack developer with 14 years in PHP, Laravel, WordPress,...

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

Share this post
Manoj Kumar
Manoj Kumar

Senior full-stack developer with 14 years in PHP, Laravel, WordPress, and AWS. Building products under the FluxWillow umbrella.