This post describes the internals of wordpress:php7.4-fpm-alpine Docker image. The source code of this image can be found here.
The wordpress container does nothing but run the following command and wait for it to end which it never does as it spins up a server that listens for incoming connections forever:
$ docker-entrypoint.sh php-fpm
The file docker-entrypoint.sh can be found in /usr/local/bin/docker-entrypoint.sh inside the container. It is instructive to read and understand this file. It will do some setup etc. such as installing wordpress files in the WORKDIR which defaults to /var/www/html and ends with
exec "$@"
what above line does is to run php-fpm as if it were a continuation of the docker-entrypoint.sh script [1].
php-fpm is PHP’s Fast CGI processor which spins up a server listening at port 9000.
By inspecting the docker image (docker image inspect wordpress:php7.4-fpm-alpine) one can see from where the script installs PHP. This is given by the PHP_URL in below:
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PHPIZE_DEPS=autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c",
"PHP_INI_DIR=/usr/local/etc/php",
"PHP_EXTRA_CONFIGURE_ARGS=--enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi",
"PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64",
"PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64",
"PHP_LDFLAGS=-Wl,-O1 -pie",
"GPG_KEYS=42670A7FE4D0441C8E4632349E4FDC074A4EF02D 5A52880781F755608BF815FC910DEB46F53EA312",
"PHP_VERSION=7.4.5",
"PHP_URL=https://www.php.net/get/php-7.4.5.tar.xz/from/this/mirror",
"PHP_ASC_URL=https://www.php.net/get/php-7.4.5.tar.xz.asc/from/this/mirror",
"PHP_SHA256=d059fd7f55bdc4d2eada15a00a2976697010d3631ef6f83149cc5289e1f23c2c",
"PHP_MD5=",
"WORDPRESS_VERSION=5.4",
"WORDPRESS_SHA1=d5f1e6d7cadd72c11d086a2e1ede0a72f23d993e"
]
Also we can see from PHP_EXTRA_CONFIGURE_ARGS that the php-fpm process will run as www-data. There are also references to www-data in docker-entrypoint.sh.
TIP: If you want to change the directory in which wordpress gets installed, simply change the –workdir to the location in which you want wordpress to be installed.
Pay attention to following security note from Docker website:
WARNING: the FastCGI protocol is inherently trusting, and thus extremely insecure to expose outside of a private container network — unless you know exactly what you are doing (and are willing to accept the extreme risk), do not use Docker’s
--publish(-p) flag with this image variant.
WordPress Boot Sequence
There are two great articles explaining how wordpress loads up when a request is received:
- https://theme.fm/wordpress-internals-how-wordpress-boots-up-part-2/
- https://theme.fm/wordpress-internals-how-wordpress-boots-up-part-3/
The bad: it looks like this boot sequence happens on EVERY request. That is why you don’t need to refresh or restart anything if you make changes to wp-config.php for example.
The boot sequence is this. Under /var/www/html there is an index.php which loads wp-blog-header.php which loads wp-load.php which loads wp-config.php which loads wp-settings.php which loads as many as 36 files.
bash-5.0# grep -n wp-blog-header.php index.php
4: * wp-blog-header.php which does and tells WordPress to load the theme.
17:require __DIR__ . '/wp-blog-header.php';
bash-5.0# grep -n wp-load.php wp-blog-header.php
13: require_once __DIR__ . '/wp-load.php';
bash-5.0# grep -n wp-config.php wp-load.php
4: * and loading the wp-config.php file. The wp-config.php
8: * If the wp-config.php file is not found then an error
10: * wp-config.php file.
12: * Will also search for wp-config.php in WordPress' parent
27: * If wp-config.php exists in the WordPress root, or if it exists in the root and wp-settings.php
28: * doesn't, load wp-config.php. The secondary check for wp-settings.php has the added benefit
34:if ( file_exists( ABSPATH . 'wp-config.php' ) ) {
37: require_once ABSPATH . 'wp-config.php';
39:} elseif ( @file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! @file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) {
42: require_once dirname( ABSPATH ) . '/wp-config.php';
76: /* translators: %s: wp-config.php */
78: '<code>wp-config.php</code>'
83: __( 'https://wordpress.org/support/article/editing-wp-config-php/' )
86: /* translators: %s: wp-config.php */
88: '<code>wp-config.php</code>'
bash-5.0# grep -n wp-settings.php wp-config.php
108:require_once ABSPATH . 'wp-settings.php';
Request Flow
in my nginx conf, I have
location / {
try_files $uri $uri/ /index.php?$args;
}
Now when a request comes in for https://mywordpresssite.com/2020/04/22/e2e-connected-visibility-platform/ nginx will first see if there is a resource with that name. There isn’t any so nginx will perform internal redirect and attempt to serve index.php?2020/04/22/e2e-connected-visibility-platform. This will kick-in the index.php under /var/www/htmland the wordpress boot sequence. So requests for pretty much all resources are going through index.php which acts as the Main method.
Bonus
You can extend the php7.4-fpm-alpine image and install Xdebug and Python in it as follows:
# To build a docker image from this Dockerfile run:
# docker image build . -t my-custom-wordpress-image
FROM wordpress:php7.4-fpm-alpine
# Install Xdebug
RUN apk --no-cache add pcre-dev ${PHPIZE_DEPS} \
&& pecl install xdebug-3.1.6 \
&& docker-php-ext-enable xdebug
# Install Python
ENV PYTHONUNBUFFERED=1
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools