docker安装nginx和php网页文件显示file not found

备注:老外的文章,很管用, 百度了2天没答案,看这个10分钟解决问题,为了严谨,我就不翻译了,下方标红处是解决办法。

Running nginx and PHP-FPM in separate Docker containers

Published 2019-06-23.

This is a story of how setting up some game servers turned into an exploration of the seemingly-uncharted depths of FastCGI.

If you’re impatient and just want to know how to implement nginx and PHP-FPM on Docker yourself, skip to the end.

Backstory

I’ve recently been doing some upgrades to my homelab. The main issue with my homelab for a long time has been that my only compute server was running Windows Home Server 2011. At the time I set up the server I was doing mostly .NET development (long before .NET Core) so this decision kind of made sense at the time, but what I didn’t realise until later is that Windows Home Server artificially limits your system’s memory to 8GiB. Given that due to the bloat of Windows this server used 3.5GiB at idle, this really didn’t leave much useable memory.

I’ve recently obtained a new compute server with (after a small upgrade) 2x E5-2643 v2 processors and 84GiB of memory. In order to utilise this capacity without paying exorbitant licensing fees, Linux is the only option.

Another issue I had with my old server was organisation. I had lots of small projects (often unfinished) lying around and working on one would often break another. So I figured the new server was the perfect time for a fresh start, and a good opportunity to learn Docker.

The first thing I wanted to get running on my new server was Pterodactyl. Pterodactyl is a modern control panel for game servers and it boasts lots of trendy technology. PHP 7.2, nginx, MariaDB, Redis, NodeJS, and yes, Docker. But as I started following the setup instructions I was dismayed! They recommended installing almost everything on bare metal, with only the game servers running in Docker. This simply wouldn’t do, I refused to have my dreams of a tidy server crushed so quickly.

I did a bit of searching for other people who’d set up Pterodactyl in Docker and I found this project which seems easy to use. But it has a flaw that the perfectionist in me could not ignore: it runs php- fpm and nginx within the same container. This breaks the golden rule of Docker, “one process per container”.

And so I realised I would have to build my own solution. (Or really, I convinced myself I would have to build my own because I wanted to get my hands dirty.)

Introductions

First, I’d like to introduce the technologies I’m going to be talking about.

PHP 7.2

PHP is something of an infamous language. It has a reputation for (among other things) being fairly slow. Most of this reputation probably comes from improperly optimised apps, since it’s a language often taught to beginners and so many junior developers use it extensively. Some of it probably also comes from misconfigured infrastructure, since some vital performance features (such as OPcache) used to be disabled by default. And some of it probably came from Zend Engine (the software responsible for running PHP) itself. (Facebook famously found Zend Engine so slow that they built their own software, HHVM, to run PHP instead.)

Many of these issues have been fixed in PHP 7. OPcache is enabled by default, and the new Zend Engine III provides substantial performance improvements. For example, WordPress 5.0 runs slightly faster in PHP 7.2 than it did on HHVM, and on PHP 7.2 it runs more than 2.5 times as fast as it did on PHP 5.6. (Source)

PHP-FPM

The traditional way to run PHP is using the mod_php Apache module. This module means that the PHP interpreter is contained within every Apache worker process. This works fairly well, but it has the significant drawback that the PHP interpreter is loaded when responding to every request, even requests for non-PHP files such as images. Additionally it’s not compatible with mpm_event, a module for Apache that improves performance when serving static files.

PHP-FPM solves these problems. PHP-FPM runs PHP processes separately to the web server. This means that the web server doesn’t need to include a PHP interpreter, instead when a PHP file is requested the web server can talk to PHP-FPM via the FastCGI protocol and request for PHP-FPM to do the necessary processing. The FastCGI protocol means that the web server and PHP can communicate over TCP/IP, so they don’t even need to be on the same physical server.

nginx

Another advantage of using FastCGI instead of mod_php is that you are no longer tied to Apache. nginx (pronounced engine-x) has a reputation for impressive performance. In modern times Apache is very close to nginx’s performance, but in many workloads nginx is still just a touch faster. nginx’s configuration system also feels a lot cleaner to me than Apache’s.

Docker

Docker is a platform for creating and running containers. A container is kind of like a VM, except that it shares the host OS’s kernel. This reduces overhead by eliminating the need for the hypervisor to provide emulated hardware for the guest kernel to interact with.

Docker also includes tooling to automatically build images, which are basically snapshots that containers can be started from. An image can contain your entire application, as well as any dependencies it needs, so that when you start a container from that image your application is immediately useable.

As of recently, Docker also includes Swarm, a way to manage Docker containers across clusters of servers. Swarm mode has a number of limitations when compared to standalone containers, such as only being able to use prebuilt images. Bind mounts are also difficult to work with on Swarms and should be avoided.

Alpine Linux

Alpine Linux is a tiny, efficient Linux distro based on musl and BusyBox. It’s perfect for Docker because its small size allows for producing small images.

With the introductions out of the way, let’s dive in!

File accessibility

Most guides for setting up PHP-FPM and nginx assume that both will be running on the same server/in the same container. This means you don’t really need to understand which process accesses which files, you can just give both processes access to them.

When you’re running across multiple containers, it’s helpful to understand what the exact requirements are. I had guessed that nginx would need access to static files, but I wasn’t sure if it would send the PHP files to PHP-FPM via the FastCGI connection, or if PHP-FPM needed access to the files itself. After some research and experiments, here’s a table which shows which files each process needs access to:

if your nginx config checks for the existence of PHP files before passing the request to FastCGI then nginx needs access to the PHP files.

if (!-f $document_root$fastcgi_script_name) { # nginx requires access to the PHP files to check this
  return 404;
}

Otherwise, nginx does not require access to PHP files.

Of course, the easiest solution is simply to share all the files between both containers. This can easily be accomplished in Docker with a named volume.

Permissions

Be wary when sharing volumes between containers that are based on different Linux distributions. On Debian the user www-data has a uid of 33 and the group www-data has a gid of 33. On Alpine these id numbers correspond to the xfs (X Font Server) user and group. Alpine has, as far as I can tell, a www-data group with gid 82 and may have a www-data user with uid 82. (The www-data user is present in the php:7.2-fpm-alpine image but not in the nginx:stable-alpine image.)

File locations

While working out the specifics of which process needs access to which files, I noticed something in the nginx config that offended me. Nginx recommends setting SCRIPT_FILENAME to be $document_root$fastcgi_script_name. Notice the problem? SCRIPT_FILENAME specifies to PHP-FPM where it should look for the PHP file, but $document_root$fastcgi_script_name is the absolute path to where nginx found the PHP file. If you’re running nginx and PHP-FPM on the same server then this will be the same location (unless you’re doing something funky with your filesystem like using bindfs to put the files in different places for different users) but if nginx and PHP-FPM are on different servers (or containers) then there’s no guarantee that this location would be the same.

Now of course, the easy solution here would’ve been to ensure that the locations are the same. Then everything would’ve worked just fine. But I would’ve known, deep down, that it was wrong.

So I spent the next few hours trying to resolve it. The general idea of what I was trying to do was to have nginx simply pass $fastcgi_script_name and to have PHP-FPM prepend the absolute path to the application.

Initially I saw the tantalising prefix property within PHP-FPM.conf, with the vague but promising description “Specify prefix for path evaluation”. Unfortunately after looking through the PHP source code I found this was completely irrelevant for my purposes, as instead of being added as a prefix to PHP files it is added as a prefix to other config options, such as listen and slowlog.

Next I took a look at chroot, but this prevents PHP from being able to access any files outside of the directory. With Pterodactyl there is a main application directory, and within it a public directory that nginx should serve. So if I used the chroot property then PHP would be unable to access the application files outside of the public directory.

And so I turned to chdir, which essentially sets the working directory for the PHP-FPM worker processes. But this wasn’t working either. To help me work out why, I used a tool called strace. strace is a debugging tool and it provides the useful ability to view all the files a process is reading. In order to use strace in Docker, the container must have the SYS_PTRACE capability. Installing strace on Alpine is easy, apk add --no-cache strace. In order to attach strace to a process you will first need to know the PID of that process, which can be found using ps. At the time I only had two nginx worker processes, so I picked one at random. strace could then be attached using strace -p <PID>. Once attached, I used a browser to request a PHP page a few times until nginx decided to handle a request using the worker I was attached to.

With this, I discovered the reason why chdir was ineffective: nginx’s $fastcgi_script_name variable includes the / at the beginning of the request (e.g. “/index.php”). Paths beginning with a slash are absolute, meaning that the working directory is ignored! I tested this theory by temporarily hardcoding SCRIPT_FILENAME to “index.php” (with no leading slash) and, lo-and-behold, the script loaded.

Now my goal was to remove the leading slash from $fastcgi_script_name. In principle this seems easy, as according to the documentation the $fastcgi_script_name is equal to the result of the first capture group from fastcgi_split_path_info. I changed the fastcgi_split_path_info to ^/+(.+\.php)(/.+)$ to remove any leading slashes. But still PHP-FPM was receiving a leading slash.

Warning

If the regular expression you’ve specified for fastcgi_split_path_info fails to match the path, nginx will silently ignore it and instead use some kind of internal default value. I wasted a lot of time due to this, I thought my changes were for some reason not taking effect or the directive was being overriden somewhere. It’d be nice if nginx returned an error in this scenario.

Anyway, what I found is that it doesn’t matter what you do in fastcgi_split_path_info, there will always be a leading slash in $fastcgi_script_name. If one isn’t captured by the RegEx, something within the FastCGI module just prepends one. If you want to remove it, you have to use other means:

set $filename "index.php";
if ( $fastcgi_script_name ~ "^/+(.*)$" ) {
   set $filename $1;
}
fastcgi_param SCRIPT_FILENAME $filename;

Note

Overwriting the $fastcgi_script_name variable directly with set is not allowed, you’ll get an error saying [emerg] the duplicate "fastcgi_script_name" variable

Note

According to this comment, PHP-FPM’s behaviour around SCRIPT_FILENAME breaks the RFC 3875 standard. This would explain why it’s clunky to get nginx to send the right data, since nginx expects FastCGI responders to conform to the standard.

And now it works! Nginx and PHP now only have to care about their own files, not each others.

Was it worth spending so much time on this? Probably not. But at least I learned some things along the way.

docker-nginx-for-PHP-FPM

It turns out that (once you’ve figured out the above) it’s possible to create a very generalised nginx image. So I’ve done just that! I have published docker-nginx-for-PHP-FPM on DockerHub. If you’d like to see an example of how it can be used, check out the docker-compose.yml file included in my docker-pterodactyl-panel repository.

This image also works perfectly in Swarm Mode, I’m using it to host my own production Pterodactyl setup.

### 解决 `docker-compose` 启动时未找到配置文件的问题 当运行 `docker-compose up` 命令并遇到错误提示 `no configuration file provided: not found` 时,通常是因为当前目录下缺少有效的 `docker-compose.yml` 文件[^1]。以下是可能的原因以及解决方案: #### 可能原因分析 1. **缺失 `docker-compose.yml` 文件** 如果当前工作目录中不存在名为 `docker-compose.yml` 的文件,则会触发此错误消息[^2]。 2. **指定路径不正确** 用户可以通过 `-f` 参数手动指定 `docker-compose` 配置文件的位置。如果指定了错误的路径或文件名,也会引发该问题[^3]。 3. **权限不足** 即使存在 `docker-compose.yml` 文件,但如果用户对该文件没有读取权限,同样会出现类似的错误提示[^1]。 4. **拼写错误** 文件名称大小写敏感,在某些操作系统(如 Linux 或 macOS)中,`DockerCompose.yml` `docker-compose.yml` 被视为不同的文件[^2]。 --- #### 解决方案 ##### 方法一:确认当前目录下的配置文件是否存在 确保当前目录中有名为 `docker-compose.yml` 的文件。可以使用以下命令验证: ```bash ls -l docker-compose.yml ``` 如果没有发现该文件,请检查项目结构是否完整,并上传丢失的配置文件[^2]。 ##### 方法二:通过 `-f` 明确指定配置文件位置 如果配置文件不在默认位置或者重命名过,可通过 `-f` 参数显式声明其路径。例如: ```bash docker-compose -f /path/to/docker-compose.yml up ``` 注意替换 `/path/to/` 为实际路径[^3]。 ##### 方法三:修复文件访问权限 假如已经确认有正确的 `docker-compose.yml` 文件但仍报错,可能是由于权限设置不当引起的。尝试赋予必要的读取权限给目标文件: ```bash chmod +r docker-compose.yml ``` ##### 方法四:校验 YAML 格式的合法性 即使文件存在且可被正常加载,语法上的任何小瑕疵都可能导致解析失败从而间接表现为找不到有效配置的情况。推荐在线工具来检测潜在格式问题[^1]。 --- ### 示例代码片段 下面展示了一个简单的 `docker-compose.yml` 文件模板供参考: ```yaml version: '3' services: web: image: nginx:latest ports: - "80:80" db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: example ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值