Setting up Photon locally for WordPress image transformations and WordPress VIP hosting offers an image transformation API.

Although their exact implementation remains unknown to the public, they use a customized version of Photon, which is open-source.

If you favor a prescriptive syntax over a descriptive syntax for your responsive images, then Photon or a comparable solution is indispensable or, at the very least, extremely useful.

At present, the selected hosting for "my" client, WordPress VIP, does not provide a local development environment with the image transformation capabilities.

VIP File System, Cron control, and Page cache services are not built-in. When developing features for an application that relies on these services, it is strongly recommended to stage changes and perform tests on a non-production VIP Platform environment.

Not being able to test services locally but only on the hosting environment is not the most efficient approach for rapid delivery.

Consequently, I decided to try to set up Photon locally on my own.

Setting up Photon locally

While most code from is open-source, the infrastructure and configuration details aren't publicly available. We can only make educated guesses about the PHP extensions, libraries, and so on, installed on their server.

Dockerized Photon: the starting point

Chris Zarate shared a Dockerized Photon, which served as an excellent starting point.

Lacking any official documentation regarding Photon's requirements, this provided the technology stack needed to run it. At least, the stack required six years ago, which is the date of the last commit in the repository.

I didn't expect it to work out of the box, and indeed, it didn't. Some of the libraries were no longer accessible. However, since most of the requirements are similar to a more involved LAMP stack, it was easy to find newer versions or alternatives to the requirements.

Photon's configuration

Photon's main entry file expects a config.php file, which is not made available - for good reasons, presumably.

This forced me to scan the code and try to decipher some of the logic, as things weren't functioning even after setting up the infrastructure.

Ultimately, even though I'd rather not know anything about Photon's internals, this turned out to be advantageous. It led me to find the override_raw_data_fetch filter, which allowed me to control the image loading process.

Photon up and running

Check this video, where I get the Photon service up and running and where I explain some extra things.

There are things to improve, but after weighing the trade-offs between development time, complexity, and the specific needs of my project, I settled to stop at this point.

I might return to Photon and implement some image caching and other good things.

Serving the WordPress images: different approaches

Depending on yor WordPress installation, you might opt to modify the attachment URLs with a function like wp_get_attachment_image_src, use a rewrite rule to redirect all images to the container, or set up a proxy.

All are valid solutions in different circumstances.

Integrating Photon with DDEV

As the project I'm working on uses DDEV, it made sense to add it as an additional service rather than run it as a standalone Docker container outside DDEV.

Creating an Additional Docker Compose File

This can be achieved by creating an additional Docker Compose file.

While the documentation on setting up additional services is brief, they do have a contribution repository with plenty of examples.

version: '3.6'

        container_name: ddev-${DDEV_SITENAME}-photon
        hostname: ${DDEV_PROJECT}-photon
        build: ./photon/
            - '80'
            com.ddev.approot: $DDEV_APPROOT
            - HTTP_EXPOSE=8078:80
            - HTTPS_EXPOSE=8079:80
            - SERVER_NAME=ddev-${DDEV_PROJECT}-photon
            - ../public/wp-content/uploads:/var/www/html/uploads

Nginx Configuration: Proxy or Redirect

It also required the modification of the Nginx config file. I opted to go with a proxy.

location ~ /wp-content/uploads/.*(jpe?g|png|webp|gif)$ {
    rewrite /wp-content/uploads/(.*)$ /$1 break;
    proxy_pass http://photon;

But I had it working as redirect before that:

location ~* (.*/wp-content/uploads/)(.*jpe?g|png|webp|gif)$ {
    return 307 https://$host:8079/$2$is_args$args;

The 307 status is "Temporary Redirect"; not that it would matter locally.