Preface
Why did I write this article? As you know, many application deployments require an LNMP environment. Setting up this environment is neither too difficult nor too simple. Moreover, there are now various Linux panels that thoughtfully provide one-click, one-stop installation. In the beginning, I was also used to using the Baota Linux panel, and later I also tried the 1Panel panel. Actually, both are quite good.
Later, mainly because I often tinker around, moving various data back and forth, when it comes to data migration, the advantages of docker come into play. For example, I suddenly wanted to repurpose my dusty first-generation M1 Mac mini and deploy all my applications in containers. At this point, migrating the applications I originally deployed from source code in the aaPanel became very troublesome. Of course, another way is to install aaPanel directly via docker, but personally, I don't like this approach. I feel it should either be bare-metal deployment (Linux panels are originally born for bare metal) or fully containerized, which is why this article exists.
One benefit of the panel is its flexible support for multi-site, multiple PHP versions, and multiple databases, as shown in the figure below:

Database switching is easy, just change the pointer in the configuration file, but switching PHP versions this way is indeed very convenient. If I want to replace it with a containerized environment, ideally the following goals should be achieved:
Reuse
Being able to, just like using the panel, create a new site simply by adding the corresponding directory (and a corresponding conf configuration file for the site).
Flexible switching
If you need to change the php version, it should also be simple and convenient (taking just a few seconds).
So in summary, you only need to establish one nginx container and one php container (by "one" here, I mean only one is needed per version, such as the php7.4 used below) to allow multiple sites to use them simultaneously.
Note: Let me briefly mention my philosophy here. I rarely use docker compose when using docker, because my philosophy is reuse. I don't pull up a bunch of containers every time. For example, if I have a mariadb container, and all my applications that need a database can use mariadb, I will use this one, which also makes backup convenient. Of course, this is only for my environment and habits; if it is another environment with application isolation requirements, that's a different story.
Deploying nginx and php7.4 fpm containers
Finally getting to the main topic, next we will proceed with the practical operation, creating the bridge and establishing an nginx container and a PHP7.4 fpm container in sequence:
Create bridge
Since a bridge named public-net will be used below, it needs to be created in advance:
docker network create public-net
Deploying the nginx container:
docker run --name=nginx -d --restart=always --net=public-net \
-p 9000:80 \
-v /docker/nginx/html:/usr/share/nginx/html \
-v /docker/nginx/conf/conf.d:/etc/nginx/conf.d \
-v /docker/nginx/log:/var/log/nginx \
nginx:latest
-p 9000:80 \
docker cp nginx:/usr/share/nginx/html /docker/nginx/
docker cp nginx:/etc/nginx/conf.d /docker/nginx/conf/
Deploying the php7.4 fpm container:
docker run --name=php_74 -d --restart=always --net=public-net \
--privileged=true \
-v /docker/nginx/html:/var/www \
php:7.4-fpm
Multi-site sharing with a single nginx container and a single php7.4 fpm
-v /docker/nginx/log:/var/log/nginx \
Key points:
nginx:latest
Note: nginx has default files. Containers with default files cannot have empty folders directly mounted with -v. You need to first create a container without the -v parameter, copy the files out using commands, delete the container, and then recreate it using -v to mount the volume. Copying files is similar to the following:
docker cp nginx:/usr/share/nginx/html /docker/nginx/
docker cp nginx:/etc/nginx/conf.d /docker/nginx/conf/
docker run --name=php_74 -d --restart=always --net=public-net \
location ~/ .php { document_root$fastcgi_script_name;;
--privileged=true \-v /docker/nginx/html:/var/www \ php:7.4-fpm
location ~/ .php {fastcgi_script_name;;
This is basically done. But how do we achieve the reuse and flexible switching mentioned above?
Directory structure:
Actually, the directory structure is very simple, for example: