Combined or separate containers for running nginx and php in production?

TLDR: In production, should I use two separate containers (one running nginx and one running php-fpm), or a single container that runs both?

Long Version

As far as I understand it, nginx needs access to the static files in my project but not the PHP files. Separating static assets from PHP files seems tricky and doesn’t strike me as being a problem worth solving. So let’s just ignore this distinction and assume that nginx needs access to all the app files.

If you use separate containers for php-fpm and nginx, then you need to expose your app files via a volume so that nginx has access to them. This is okay for local development but is not viable in production where we want to distribute our app files via an image.

Therefore, it seems like using a single container to run both services is the only reasonable way to use nginx and php-fpm in production. That way, nginx has access to the app files without resorting to any hacky workarounds.

(And if that’s the case, then it stands to reason that you should use the same combined nginx and php-fpm image in your local development environment as well for consistency with production. Yet all the tutorials I have seen for setting up a development environment use two separate images.)

What is the best practice for using ngix and php-fpm in production? Should I use a single container that runs both nginx and php-fpm, or is there a viable/preferred way to use two separate containers?

update: Edited the shared example code the original did not define the top level volume and the name of the top level volume cannott be a dynamic yaml key. The new code refers to “data” in the services and the name is defined as a parameter of the top level volume definition. Thanks @meyay for pointing out the mistake.


Running separate containers for multiple services is a general rule which also applies to NginX and PHP FPM. Escpecially in poduction. You may want to be able to scale up and down just one service or run on multiple servers in a cluster. Not to mention different dependencies and security. Each container can access only files necessary to run that service in the container.

Yes, running applications in production can be tricky sometimes, that is why people can pay for professionals to do it right. If it is not worth it ti you, you and you don’t need multiple PHP and nginx instances independently, you can try to run it in a single container, but doing it would be tricky as well. You would need to run a process manager in the container like supervisor or s6-init.

If the application is written in a way that doesn’t allow separeting static files and PHP files, that seems to be a bad design in 2024. I know CMSs can include modules that can include static files liek css and js, but those files can often be cached into a single folder or sometimes even a single file when the filetypes are the same. In that case there os often a public or similar folder which is the only one to mount to nginx or other webserver and everything else can go to the PHP container only.

When you have all the files in the image and you mount only uploaded files and other kind of data into the container, you can still use a volumes where the volume name includes the version number of the image. For example:

services:
  nginx:
    volumes:
      - type: volume
        source: data
        target: /docrootpath
    depends_on:
      - php
  php:
    image: yourapp:${APP_VERSION}
    volumes:
      - type: volume
        source: data
        target: /yourapp-path/public

volumes:
  data:
    name: "projectname_public_${APP_VERSION}"

Then yo have to reattach the new volume by using --renew-anon-volumes

docker compose up -d --renew-anon-volumes

Yes, I know, it is not anonymous, but it worked for the named volume as well. Without this, the new volume would be created but not attached to the container.

Since the nginx depends on the PHP, when a new version is set in the .env file for example, it will chaneg the image tag and also create a new volume before nginx starts, the public files are copied to the volume so nginx can mount it. The old volume will still be there. You can handle that manually after checking that everything is right so if you need, you can roll back to the old version, or you can write a script for the deployment which immediately deletes it, or you can run it in a cronjob to remove old versions of these files. Of course, if that public folder is really static, it is not even necessary to keep it since you can roll back and the old image will start to copy the old files to the old volume again.

1 Like

I guess that depends on the context and the person you ask :sweat_smile:

We run some tiny PHP sites in combined PHP-Apache containers (link). I never bothered to understand FPM, so I have a single container (webserver+PHP), which I can scale.

The PHP containers are next to my NodeJS containers, same structure, same scaling mechanism, both proxied to by Traefik (sometimes by nginx-proxy).

So it really depends on your setup, how much traffic you get, if going deeper and separating for better performance and scaling separately matters.

The WordPress Docker images (link) also come in FPM and Apache variations, so I guess both are accepted standards.

1 Like

Yes, but that image is an httpd image with PHP as a module, not as a service. NginX has no such module as far as I know, but the topic was about NginX and PHP.

Yes, together. It is not necessarily a problem, but could be.

But you are absolutely right, production could mean different thing in different use cases.

Note: I updated my previous post as the code was wrong

1 Like

Apologies for the late reply and thanks for providing such a detailed explanation along with a code example.

I opted to use separate containers with the unique volume names like you suggested. I like how we can use the unique volume names as a cache busting technique, forcing it to create new volumes if there have been changes to the app.

This helped me reach a deadline which I otherwise likely would have missed so thanks again for your help!

There are many ways to skin a cat :smile:

We are already committed to using nginx on this project but Traefik looks cool. I’ll probably give a shot on a side project or something.

You can still use nginx as reverse proxy (nginx-proxy, companion handles LetsEncrypt TLS), but don’t necessarily need it for PHP.