6 min read

Using Docker to Solve PHP Version Compatibility Issues: A Practical Guide

When developing modern web applications, you often face a common challenge: your application requires a specific PHP version that differs…

Laravel PHP MySQL Docker Web Development
Using Docker to Solve PHP Version Compatibility Issues: A Practical Guide

When developing modern web applications, you often face a common challenge: your application requires a specific PHP version that differs from what’s available on your production server. Rather than compromising your application’s requirements or risking server-wide changes, Docker offers an elegant solution.

In this guide, I’ll walk you through a real-world scenario: deploying a Laravel application that requires PHP 8.3 on a server that only has PHP 8.2 installed.

The Problem: PHP Version Mismatch

Imagine you’ve developed a new Laravel application with Filament admin panel that requires PHP 8.3, but your production server runs PHP 8.2.26 and hosts several other PHP applications that you can’t disrupt.

When attempting to install dependencies with Composer, you encounter this error:

Your lock file does not contain a compatible set of packages. Please run composer update.
  Problem 1
    - openspout/openspout is locked to version v4.29.1 and an update of this package was not requested.
    - openspout/openspout v4.29.1 requires php ~8.3.0 || ~8.4.0 -> your php version (8.2.26) does not satisfy that requirement.

Four Potential Solutions

When facing PHP version incompatibility, you typically have four options:

  • Upgrade PHP directly on the server — Risky for existing applications
  • Downgrade your application’s dependencies — Not always possible or desirable
  • Use separate PHP-FPM pools — Requires complex configuration
  • Use Docker to isolate your application — Our recommended approach

Docker provides the perfect balance of isolation, simplicity, and maintainability. Let’s see how to implement it.

Solution: Containerizing with Docker

We’ll isolate our Laravel application in its own environment while maintaining seamless integration with the existing server infrastructure.

Step 1: Create Docker Configuration Files

First, we need to create three key configuration files:

docker-compose.yml:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: project_app
    volumes:
      - ./:/var/www/html
    networks:
      - project_network
    restart: unless-stopped

  web:
    image: nginx:alpine
    container_name: project_web
    ports:
      - "8080:80"
    volumes:
      - ./:/var/www/html
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app
    networks:
      - project_network
    restart: unless-stopped

networks:
  project_network:
    driver: bridge

Dockerfile:

FROM php:8.3-fpm

# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    libzip-dev \
    libicu-dev \
    zip \
    unzip \
    nodejs \
    npm

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
RUN docker-php-ext-configure intl && docker-php-ext-install intl

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Set working directory
WORKDIR /var/www/html

docker/nginx/default.conf:

server {
    listen 80;
    server_name localhost;
    root /var/www/html/public;

    index index.php;

    # Add these lines to recognize the forwarded protocol
    real_ip_header X-Forwarded-For;
    set_real_ip_from 172.0.0.0/8;
    real_ip_recursive on;

    # Trust the X-Forwarded-Proto header
    map $http_x_forwarded_proto $real_scheme {
        default $http_x_forwarded_proto;
        '' $scheme;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        
        # Add this line to pass the correct scheme to PHP
        fastcgi_param HTTPS on;
    }
}

Step 2: Modify Apache Configuration for Proxying

Since we already have Apache configured with SSL certificates, we’ll modify the VirtualHost configurations to proxy requests to our Docker container:

SSL Configuration (example.com-le-ssl.conf)

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerAdmin webmaster@localhost
    ServerName example.com
    ServerAlias www.example.com
    
    # Proxy to Docker container
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
    
    # Add these headers to inform the application it's behind HTTPS
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Port "443"
    
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    Include /etc/letsencrypt/options-ssl-apache.conf
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
</VirtualHost>

HTTP to HTTPS Redirect (example.com.conf)

<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    ServerName example.com
    ServerAlias www.example.com
    
    # We're only handling redirects here
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    
    # Redirect all HTTP traffic to HTTPS
    RewriteEngine on
    RewriteCond %{SERVER_NAME} =example.com [OR]
    RewriteCond %{SERVER_NAME} =www.example.com
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

Step 3: Enable Apache Proxy Modules

sudo a2enmod proxy proxy_http headers
sudo systemctl restart apache2

Step 4: Start the Docker Environment

docker-compose up -d

Step 5: Install Dependencies and Build Assets

docker-compose exec app composer install
docker-compose exec app npm install
docker-compose exec app npm run build

Step 6: Configure Laravel for HTTPS

Update the .env file to ensure Laravel understands it's being served over HTTPS:

APP_URL=https://www.example.com
ASSET_URL=https://www.example.com

Additionally, update the AppServiceProvider.php to force HTTPS:

public function boot()
{
    if (app()->environment('production')) {
        \URL::forceScheme('https');
    }
}

Step 7: Set Proper Permissions

docker-compose exec app chown -R www-data:www-data /var/www/html/storage
docker-compose exec app chmod -R 775 /var/www/html/storage

Common Challenges and Their Solutions

Throughout this process, you may encounter several challenges. Here’s how to resolve them:

1. Missing PHP Extensions

When running composer install, you might discover missing PHP extensions. Update your Dockerfile to include the necessary extensions:

# Install system dependencies
RUN apt-get update && apt-get install -y \
    libzip-dev \
    libicu-dev \
    # other dependencies...

# Install PHP extensions
RUN docker-php-ext-install zip
RUN docker-php-ext-configure intl && docker-php-ext-install intl

2. Database Path Issues

If your application uses SQLite, the database path in the .env file might refer to the host path instead of the container path. Update it to:

DB_CONNECTION=sqlite
DB_DATABASE=/var/www/html/database/database.sqlite

3. Missing Frontend Assets

If the Vite manifest is missing, you need to build the frontend assets:

make npm-install
make npm-build

4. Mixed Content Warnings

If you encounter mixed content warnings because assets are being loaded over HTTP instead of HTTPS, resolve this by:

  • Setting ASSET_URL in the .env file
  • Adding URL::forceScheme('https') in the AppServiceProvider
  • Configuring Nginx to understand it’s behind an HTTPS proxy

Why This Approach Works So Well

This Docker-based solution offers several advantages:

  • Perfect Isolation: Your application runs in its own environment with exactly the PHP version it needs
  • Zero Impact on Existing Applications: Other applications on the server continue using the current PHP version
  • Reproducible Environment: The environment is defined in code and can be recreated on any server
  • Simplified Management: The Makefile makes common tasks easy to execute
  • Maintained Security: SSL termination continues through Apache with existing certificates
  • Flexibility: Easy to update PHP versions or add extensions without affecting the host system

Conclusion

By using Docker to isolate your Laravel application, you can successfully run applications requiring PHP 8.3 on a server with PHP 8.2, without disrupting other applications. This approach creates a separation of concerns that makes maintenance easier and reduces the risk of server-wide issues.

The combination of Docker for isolation, Apache for SSL termination, and environment-specific configuration in Laravel creates a robust, secure setup that bridges the gap between modern application requirements and existing server constraints.

For any web developer facing similar version compatibility challenges, this pattern provides a practical, reusable solution that maintains both application integrity and server stability.


Need Help With Your Laravel Project?

I specialize in building custom Laravel applications, process automation, and SaaS development. Whether you need to eliminate repetitive tasks or build something from scratch, let's discuss your project.

⚡ Currently available for 2-3 new projects

Hafiz Riaz

About Hafiz Riaz

Full Stack Developer from Turin, Italy. I build web applications with Laravel and Vue.js, and automate business processes. Creator of ReplyGenius, StudyLab, and other SaaS products.

View Portfolio →