Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
title: "Using DDEV to spin up a legacy PHP application"
pubDate: 2025-05-30
#modifiedDate: 2025-05-30
modifiedDate: 2026-09-24
modifiedComment: "Updated for current DDEV: the old `db` image now follows the installed DDEV version instead of a fixed one, the entrypoint patch matches the current `docker-entrypoint.sh`, and `x-ddev` keys label the services in `ddev describe`."
summary: How to use DDEV with outdated PHP and MySQL versions
author: Garvin Hicking
featureImage:
Expand Down Expand Up @@ -30,53 +31,75 @@ I quickly faced three major issues:

Thanks to the outstanding DDEV support on Discord, I was quickly able to find a way with minimal effort, just by creating very small custom, additional docker-compose YAML files.

One advantage (of many) of using DDEV instead the underlying Docker Compose is that so many things are pre-configured and "just work". So I really did not want to migrate everything to Docker Compose on my own, do my custom routing, PHP-FPM integration and whatnot.
One advantage (of many) of using DDEV instead of the underlying Docker Compose is that so many things are pre-configured and "just work". So I really did not want to migrate everything to Docker Compose on my own, do my custom routing, PHP-FPM integration and whatnot.

Just being able to "bait and switch" the PHP and DB container with a different base Docker image was all that was needed for me:

## Step 1: Base config

I created the base `~/legacyphp/.ddev/config.yaml` file manually inside my `~/legacyphp` project directory, setting `legacyphp` as the project name.

Note that I configured PHP and MySQL versions that are supported by DDEV for this first:
MySQL 8.0 here is only a placeholder that DDEV accepts. Step 2 replaces it with MySQL 5.5. There is no `php_version`, because the PHP version in the `web` container does not matter: Step 3 adds a separate container for PHP 5.3.

```yaml
# .ddev/config.yaml
name: legacyphp
type: php
docroot: htdocs
php_version: "8.3"
webserver_type: apache-fpm
database:
type: mysql
version: "8.0"
hooks:
pre-start:
- exec-host: |
DDEV_DB_LEGACY_IMAGE=$(echo "$DDEV_DBIMAGE" | sed 's/mysql-8.0/mysql-5.5/')
ddev dotenv set .ddev/.env.db.local --ddev-db-legacy-image="$DDEV_DB_LEGACY_IMAGE" >/dev/null
```

The [`pre-start` hook](https://docs.ddev.com/en/stable/users/configuration/hooks/) keeps this setup up to date. `$DDEV_DBIMAGE` is the `db` image DDEV is about to use, for example `ddev/ddev-dbserver-mysql-8.0:v1.25.4`. Changing `mysql-8.0` to `mysql-5.5` gives the old image from the same DDEV release, so it stays in sync when you upgrade DDEV.

[`ddev dotenv set`](https://docs.ddev.com/en/stable/users/usage/commands/#dotenv-set) saves that value in `.ddev/.env.db.local`, so the compose file in Step 2 can use it as `${DDEV_DB_LEGACY_IMAGE}`.

## Step 2: Rewire DB

:::note
Only needed on ARM64.
:::

Next I created the very small file `~/legacyphp/.ddev/docker-compose.db.yaml` in the same directory next to `config.yaml`:

```yaml
# .ddev/docker-compose.db.yaml
services:
db:
platform: linux/amd64
build:
args:
BASE_IMAGE: ddev/ddev-dbserver-mysql-5.5:v1.24.6
BASE_IMAGE: ${DDEV_DB_LEGACY_IMAGE}
#image: ${DDEV_DB_LEGACY_IMAGE}-${DDEV_SITENAME}-built

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is being commented on because the fix has not been released yet:

entrypoint:
- sh
- -c
- |
cp /docker-entrypoint.sh ~/docker-entrypoint.sh
sed -i '157s|.*|if false; then|' ~/docker-entrypoint.sh
sed -i '175s|.*|echo mysql_8.0 >/var/lib/mysql/db_mariadb_version.txt|' ~/docker-entrypoint.sh
# report MySQL 8.0 so the version check passes
sed -i 's|server_db_version=.*|server_db_version=mysql_8.0|g' ~/docker-entrypoint.sh
sed -i 's|database_db_version=.*|database_db_version=mysql_8.0|g' ~/docker-entrypoint.sh
# but keep the real MySQL 5.5 configuration
sed -i 's|BEST_MATCH=.*|BEST_MATCH=/etc/mysql/version-conf.d/mysql_5.5.cnf.txt|g' ~/docker-entrypoint.sh
exec ~/docker-entrypoint.sh
x-ddev:
describe-info: "Using mysql:5.5"
```

Three things are noteworthy:
A few things are noteworthy:

- Setting `linux/amd64` as the platform will require Rosetta to be available on the macOS ARM64 platform
- The `BASE_IMAGE` is set to a DDEV `db` container of legacy Docker images that are still provided.
- Changing the `entrypoint` is a workaround to prevent DDEV complaining about a mismatching MySQL version after restarting the project. The small script "tricks" the DDEV inspection into believing, the version matches the one configured in `.ddev/config.yaml`.
- Setting `linux/amd64` as the platform will require Rosetta to be available on the macOS ARM64 platform.
- `BASE_IMAGE` uses the `${DDEV_DB_LEGACY_IMAGE}` value from the hook, so the old image always matches your DDEV version.
- The `image` name stops the build from overwriting DDEV's own `ddev-dbserver-mysql-8.0` image on your machine.
- Changing the `entrypoint` stops DDEV from complaining about a version mismatch when you restart the project. The container runs MySQL 5.5, but has to look like the MySQL 8.0 set in `.ddev/config.yaml`.
- [`x-ddev`](https://docs.ddev.com/en/stable/users/extend/custom-docker-services/#customizing-ddev-describe-output) adds a note to `ddev describe`, so you can see the real version even though `.ddev/config.yaml` says 8.0.

## Step 3: Rewire PHP

Expand All @@ -85,6 +108,7 @@ Using a different PHP version is just a few lines more work, because we are not
This is done via the file `~/legacyphp/.ddev/docker-compose.php.yaml`:

```yaml
# .ddev/docker-compose.php.yaml
services:
php:
container_name: ddev-${DDEV_SITENAME}-php
Expand All @@ -106,26 +130,40 @@ services:
- NEW_GID=${DDEV_GID}
- DDEV_PHP_VERSION
- IS_DDEV_PROJECT=true
x-ddev:
describe-url-port: |
PHP 5.3 shell:
ddev ssh -s php -u devilbox
describe-info: "Using PHP 5.3"
ssh-shell: bash
web:
x-ddev:
describe-url-port: |
PHP 5.3 shell:
ddev ssh -s php -u devilbox
describe-info: "Using PHP 5.3"
depends_on:
- php
```

Note here that we use `devilbox/php-fpm` with our needed version, and a bind-mount takes care the PHP container can access our main project root directory.

A special mount of `~/legacyphp/.ddev/php/` is included so that we can control the `php.ini` configuration, if needed. For example you could disable the OPCache+APC in case you're doing some legacy benchmarking that should not be falsified via caching, I created a very small file `~/legacyphp/.ddev/php/php.ini` file with the contents:
The `x-ddev` blocks only change what `ddev describe` shows, but they save confusion later, because otherwise nothing hints that PHP runs in another container. The longer note goes in `describe-url-port` because that column is wider than `describe-info`. `ssh-shell: bash` is useful too, because custom services use `sh` by default.

A special mount of `~/legacyphp/.ddev/php/` is included so that we can control the `php.ini` configuration, if needed. For example, you could disable OPcache and APC when doing legacy benchmarking that caching would distort. I created a very small `~/legacyphp/.ddev/php/php.ini` file with the contents:

```ini
# This is an example.
# apc.enabled=Off
# opcache.enable=Off
; .ddev/php/php.ini
apc.enabled=Off
opcache.enable=Off
```

## Step 4: Utilize the PHP container with an Apache proxy

To execute PHP with our external PHP Docker image, I created the following file in `~/legacyphp/.ddev/apache/apache-site.conf`:

```apache
# .ddev/apache/apache-site.conf
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =https
Expand All @@ -134,6 +172,9 @@ To execute PHP with our external PHP Docker image, I created the following file
SetEnvIf X-Forwarded-Proto "https" HTTPS=on

Alias "/phpstatus" "/var/www/phpstatus.php"
<Location "/phpstatus">
SetHandler "proxy:unix:/var/run/php/php-fpm.sock|fcgi://localhost"
</Location>
DocumentRoot /var/www/html/htdocs
<Directory "/var/www/html/htdocs">
AllowOverride All
Expand Down Expand Up @@ -164,9 +205,11 @@ Your MySQL connection id is 5
Server version: 5.5.62-log MySQL Community Server (GPL)
```

`ddev describe` shows the `x-ddev` notes from Steps 2 and 3, so you can see the real PHP and MySQL versions next to the services that run them.

## Caveats

You can enter the PHP Docker container with a command like `docker exec -it -u devilbox ddev-legacyphp-php bash` if you need/want to execute PHP commands on shell-level, because the regular `web` container will run with the more recent PHP 8.3 version.
You can enter the PHP Docker container with `ddev ssh -s php -u devilbox` if you need to execute PHP commands at the shell level, because the regular `web` container runs a much more recent PHP version.
So if you need to perform composer CLI calls, be sure to do this within the matching PHP container.

Another thing to pay attention to is that if you for example want to utilize Mailpit with TYPO3's mail configuration, you can not use `localhost:1025` as an SMTP server. `localhost` in PHP's case will be that devilbox PHP container, and not the DDEV web container. Instead you need to setup `web:1025` as the hostname.
Expand Down
Loading