Running legacy PHP applications locally presents a specific set of challenges that modern framework developers rarely encounter, and this guide is a practical comparison of the tools available for solving them. If you are searching for how to set up Docker, Vagrant, MAMP, or a native PHP installation for an older PHP application - particularly one built on Zend Framework 1, CakePHP 2, or similar-era frameworks - this page covers the full picture. The content comes from maintaining and developing against legacy PHP codebases for over a decade, across dozens of different client environments. Below you will find concrete configuration examples for Docker and docker-compose, Vagrant setup with provisioning, MAMP/XAMPP quick-start guidance, native installation on macOS and Linux, multi-version PHP management, Xdebug configuration across all approaches, and a comparison to help you choose the right tool.
For the original walkthrough of setting up Apache virtual hosts for ZF1, see the Survive the Deep End guides hub.
Why Legacy PHP Needs Specific Environments
Modern PHP applications target PHP 8.x and use Composer for dependency management. Installing them locally is straightforward. Legacy applications are different:
- They may require PHP 5.6 or 7.0, which your OS no longer ships.
- They depend on extensions that have been removed from newer PHP versions (
mysqlextension,mcrypt,eregfunctions). - They expect Apache with
mod_phprather than Nginx with PHP-FPM. - They may rely on specific MySQL 5.x behaviour that differs from MySQL 8.x.
- Configuration may be hardcoded to expect paths like
/usr/local/zend/share/ZendFramework.
Your local development environment needs to replicate these constraints faithfully. Running a PHP 5.6 application on PHP 8.2 “just to see what happens” will produce a cascade of deprecation notices, fatal errors from removed functions, and subtle behavioural differences that waste hours of debugging time.
Docker Approach
Docker is the strongest option for legacy PHP environments because it gives you exact control over every component version and isolates the environment completely from your host system.
Dockerfile for Legacy PHP
1 | FROM php:5.6-apache |
This gives you PHP 5.6 with Apache, the legacy mysql extension, mcrypt, and GD - a typical stack for ZF1-era applications. The php:5.6-apache base image is maintained in the Docker official library and includes a working Apache/mod_php setup.
docker-compose for the Full Stack
1 | version: '3.8' |
Run docker-compose up -d and your application is available at http://localhost:8080. MySQL 5.7 runs alongside it, and phpMyAdmin is available at http://localhost:8081 for database inspection.
The volume mount (.:/var/www/html) maps your project directory into the container, so file changes on your host are immediately visible inside the container. On macOS and Windows, Docker Desktop’s file sharing can be slow for large projects. If you notice performance issues, consider using Docker’s cached or delegated mount options, or look into Mutagen for file synchronisation.
Xdebug in Docker
Add Xdebug to your Dockerfile:
1 | RUN pecl install xdebug-2.5.5 && docker-php-ext-enable xdebug |
Note the pinned version: Xdebug 2.5.5 is the last version supporting PHP 5.6. For PHP 7.x containers, use Xdebug 3.x instead, which has a different configuration syntax.
Xdebug 2.x configuration for Docker:
1 | xdebug.remote_enable=1 |
host.docker.internal resolves to the host machine’s IP from inside the Docker container on Docker Desktop (macOS and Windows). On Linux, you need to use the host’s actual network IP or configure Docker networking accordingly.
Vagrant Approach
Vagrant creates full virtual machines, which provides stronger isolation than Docker but with more resource overhead. It is a good choice when you need an environment that exactly mirrors a production server, including the operating system.
Vagrantfile for Legacy PHP
1 | Vagrant.configure("2") do |config| |
The provisioning script (provision.sh) installs the exact PHP version and extensions you need:
1 |
|
After vagrant up, your application runs at http://192.168.56.10. Edit files on your host; the NFS sync makes them available inside the VM in near-real-time.
Vagrant’s main advantage over Docker for legacy work is that the VM runs a full Linux kernel. If your application depends on OS-level features (cron jobs, specific filesystem behaviour, system libraries), Vagrant replicates those faithfully. The downside is startup time (minutes vs. seconds for Docker) and resource consumption.
MAMP and XAMPP for Quick Setups
MAMP (macOS) and XAMPP (cross-platform) bundle Apache, MySQL, and PHP into a single installable package. They are the fastest way to get a legacy PHP application running if you do not need exact version control.
MAMP Pro supports multiple PHP versions and lets you switch between them per-project. It also provides a GUI for configuring virtual hosts, which eliminates manual Apache configuration.
The limitations:
- PHP version availability depends on what MAMP ships. Very old versions (5.3, 5.4) may not be available in current MAMP releases.
- Extension availability is limited to what MAMP includes. Installing additional PECL extensions requires manual compilation.
- The environment does not match production. MAMP runs on macOS with its own directory structure, which differs from a typical Linux server.
- Sharing the environment with team members requires everyone to install MAMP and configure it identically. There is no version-controlled environment definition.
MAMP works well for solo developers who need a quick setup for occasional legacy work. For team environments or complex applications, Docker or Vagrant is more appropriate.
XAMPP provides a similar experience on Windows and Linux. The same limitations apply. It bundles a single PHP version per installation, so running multiple PHP versions requires multiple XAMPP installations or manual version switching.
Native Installation on macOS and Linux
On macOS, Homebrew provides multiple PHP versions through the shivammathur/php tap:
1 | brew tap shivammathur/php |
Switch between versions:
1 | brew unlink php@8.2 |
On Ubuntu and Debian, the Ondrej Sury PPA provides PHP packages for versions from 5.6 through 8.x:
1 | sudo add-apt-repository ppa:ondrej/php |
Use update-alternatives to switch the default PHP version:
1 | sudo update-alternatives --set php /usr/bin/php5.6 |
Native installation is the most performant option because there is no virtualisation layer. File system access is instant. Xdebug connects without network hops. But it pollutes your host system with old PHP versions and extensions that may conflict with other projects. If you only work on one project at a time, native installation is fine. If you juggle multiple projects with different PHP requirements, use Docker.
Managing Multiple PHP Versions
Regardless of your chosen environment, you will eventually need multiple PHP versions accessible simultaneously. Here is how each approach handles it:
| Approach | Multi-version strategy |
|---|---|
| Docker | Separate containers with different base images. No conflicts possible. |
| Vagrant | Separate VMs or use update-alternatives inside one VM. |
| MAMP Pro | Built-in version switcher in the GUI. |
| Native (macOS) | brew link/unlink to switch the CLI version. Use different FPM pools for different versions. |
| Native (Linux) | update-alternatives for CLI. Run parallel FPM services on different ports/sockets. |
For native setups running multiple projects simultaneously, configure separate PHP-FPM pools for each PHP version and point each Apache/Nginx virtual host at the appropriate FPM socket:
1 | <VirtualHost *:80> |
Database Connectivity
Legacy PHP applications often rely on the old mysql_* functions (removed in PHP 7.0) or MySQLi with specific connection patterns. When setting up your environment, verify:
- The MySQL version matches production as closely as possible. MySQL 5.7 and 8.0 have different default SQL modes, authentication plugins, and character set defaults. A query that works on 5.7 may fail on 8.0 due to stricter grouping rules or the
caching_sha2_passwordauthentication plugin. - If the application uses
mysql_*functions, you must run PHP 5.6 or use a polyfill library. There is no practical alternative. - Connection credentials in your local configuration match what your environment provides. Docker services are typically accessible by their service name (
db) from other containers and bylocalhostfrom the host.
For Docker environments, add a healthcheck to the MySQL service so your application container waits for the database to be ready before attempting connections:
1 | db: |
Xdebug Setup Across Approaches
Xdebug is non-negotiable for serious development work on legacy codebases. Stepping through unfamiliar code with a debugger is orders of magnitude faster than adding var_dump calls.
Configuration varies by Xdebug version:
Xdebug 2.x (PHP 5.6 - 7.4):
1 | xdebug.remote_enable=1 |
Xdebug 3.x (PHP 7.2+):
1 | xdebug.mode=debug |
Note the port change: Xdebug 3 defaults to port 9003 (Xdebug 2 used 9000). Update your IDE configuration accordingly.
For VS Code, the PHP Debug extension handles the IDE side. Your launch.json needs a configuration pointing to the correct port and path mappings (critical for Docker and Vagrant where the file paths inside the environment differ from your host paths):
1 | { |
Choosing the Right Tool
| Factor | Docker | Vagrant | MAMP/XAMPP | Native |
|---|---|---|---|---|
| Setup time | Medium | Medium-high | Low | Low-medium |
| Team consistency | Excellent | Excellent | Poor | Poor |
| Version control of env | Yes (Dockerfile) | Yes (Vagrantfile) | No | No |
| Performance | Good | Moderate | Good | Excellent |
| Resource usage | Low-medium | High | Low | Lowest |
| Production parity | High | Highest | Low | Medium |
| Multi-project support | Excellent | Good | Moderate | Moderate |
Choose Docker when you need reproducible environments, team consistency, and you work on multiple projects. It is the right default for most teams in 2024 and beyond.
Choose Vagrant when you need to replicate a full server environment including OS-level services, or when your deployment target is a traditional VM and you want maximum parity.
Choose MAMP/XAMPP when you need to get something running in under ten minutes and you are working solo on a single project.
Choose native installation when performance is critical (profiling, benchmarking) and you are comfortable managing PHP versions on your host system.
Whichever approach you choose, document it in your project’s README. Future you - and every other developer who touches this codebase - will benefit from clear setup instructions. For the broader context of modernising the application itself once your environment is running, see the guide on modernising Zend Framework applications.