Docker and PHP: Advanced-Level Application Containerization
Containerization of PHP applications using Docker has long ceased to be just a convenient development tool. At an advanced level, it is the key to creating scalable, secure, and reproducible environments. In this article, we will explore techniques that will help you move from basic PHP container startup to professional orchestration: multi-stage builds, image optimization, working with Docker Compose for microservice architecture, and advanced caching.
1. Multi-Stage Build: Minimal Final Image
One of the main problems with standard Docker images is their size. The base php:8.2-apache image weighs over 400 MB, and with extensions installed, it reaches 600+. This is unacceptable for production: a large image loads slower, takes up registry space, and increases the attack surface.
Multi-stage build allows you to split the process into stages: one for compiling and installing dependencies (with a full set of tools), and a second for a minimal runtime. The final image contains only compiled artifacts and necessary libraries.
Example Dockerfile for a Laravel application:
# Stage 1: Compilation and dependency installationFROM composer:latest AS vendorWORKDIR /appCOPY composer.json composer.lock ./RUN composer install --no-dev --optimize-autoloader --no-interaction
# Stage 2: Building Node resources (if there is a frontend)FROM node:18-alpine AS frontendWORKDIR /appCOPY package.json package-lock.json ./RUN npm ci --productionCOPY resources/ ./resources/RUN npm run production
# Stage 3: Final PHP imageFROM php:8.2-fpm-alpine AS productionRUN docker-php-ext-install pdo_mysql opcacheCOPY --from=vendor /app/vendor /var/www/html/vendorCOPY --from=frontend /app/public/build /var/www/html/public/buildCOPY . /var/www/html
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cacheEXPOSE 9000CMD ["php-fpm"]This approach reduces the image size from 500+ MB to ~120 MB. composer:latest and node:18-alpine are used only during the build stages and do not end up in the final layer.
2. Optimizing Docker Layer Caching
Each instruction in a Dockerfile creates a layer. To speed up rebuilds, you should place rarely changing layers as early as possible. For a PHP project, this means:
- Copy
composer.jsonandcomposer.lockbefore copying the code. - Run
composer installbefore copying the source files. - Copy the code last.
Example of an optimized instruction order:
FROM php:8.2-fpm-alpine AS baseWORKDIR /var/www/html
# Install system dependencies onceRUN apk add --no-cache $PHPIZE_DEPS \ && docker-php-ext-install pdo_mysql opcache bcmath
# Copy only dependency filesCOPY composer.json composer.lock ./RUN composer install --no-dev --no-interaction --no-scripts --prefer-dist
# Copy the rest of the codeCOPY . .
# Generate autoloader and optimizeRUN composer dump-autoload --optimizeTip: use .dockerignore to exclude vendor, .git, node_modules, and other temporary files from the build context. This will speed up context transfer to the Docker daemon.
3. Docker Compose for Microservice Architecture
Modern PHP applications rarely consist of a single service. A typical architecture includes PHP-FPM, Nginx, Redis, a task queue (RabbitMQ/Beanstalkd), and a database. Docker Compose allows you to describe the entire infrastructure in a single docker-compose.yml.
Example configuration for a PHP microservice:
version: '3.8'services: app: build: context: . target: production