Clean and functional base images providing PHP versions 7.2 to 8.3 and OpenResty web server.
By NHS Leadership Academy
This repository provides two images; one containing PHP-FPM (able to run either as a fastcgi server or cron/worker via supercronic) and another running Openresty. They are customised to the NHS Leadership Academy's various needs and include "nice to haves" such as Prometheus exporters, service management, and sane configuration for running inside a Kubernetes environment.
We use Kubernetes to make the most of our infrastructure. The majority of our applications are built on PHP and so it made sense to create base images providing a standard build to run our applications from. In keeping with Kubernetes best practice we moved to an architecture running PHP-FPM in a separate container to the Openresty web server within the same pod. Worker (or cron) is deployed separately.
This README will assume Kubernetes for context and so may make references to Kubernetes terminology such as jobs, pods etc.
Images are currently built and pushed to GitHub's container registry. Available tags are:
ghcr.io/nhsleadership/nhsl-ubuntu-openresty:latest
ghcr.io/nhsleadership/nhsl-ubuntu-phpv2:7.2-master
ghcr.io/nhsleadership/nhsl-ubuntu-phpv2:7.3-master
ghcr.io/nhsleadership/nhsl-ubuntu-phpv2:7.4-master
ghcr.io/nhsleadership/nhsl-ubuntu-phpv2:8.0-master
ghcr.io/nhsleadership/nhsl-ubuntu-phpv2:8.1-master
ghcr.io/nhsleadership/nhsl-ubuntu-phpv2:8.2-master
ghcr.io/nhsleadership/nhsl-ubuntu-phpv2:8.3-master
- These images have been built to run as the
nhsla
user with uid/gid1000
. - These images assume they are running with a read-only root filesystem.
- Writable volumes should be mounted at /nhsla/etc, /run, and /tmp otherwise the containers will fail to start. We use Kubernetes emptyDir volumes for this.
By default the PHP-FPM container runs in FPM mode. If you need to run cron jobs you should add a second container deployment, using the PHP-FPM container with the ROLE
environment variable set to CRON
or WORKER
.
We utilise Supercronic to provide Cron functionality in our image. It provides a modern Cron binary that can run as a non-root user and provides more rich logging capability.
Your crontab file should be stored at /nhsla/cron
in the image with a structure such as:
* * * * * /usr/bin/echo "This is a test cron."
The file should be owned by the nhsla
user as this is what cron runs as.
We ship a basic configuration for both PHP-FPM and Openresty which should work for most situations however there is a need to override these occasionally.
We no longer use a long list of runtime variables to do this - instead we simply overwrite the configuration using Kubernetes ConfigMaps which also allows us to work around the readOnlyRootFilesystem easily whilst keeping application specific configurations in their relevant Git repositories.
You may wish to override the following configuration files with ConfigMaps or a layered image build:
Openresty:
Openresty config (Nginx compatible): /usr/local/openresty/nginx/conf/nginx.conf
Openresty default site: /user/local/openresty/nginx/conf/site.conf
PHP-FPM:
php.ini: /nhsla/etc/php.ini
php-fpm: /nhsla/etc/php-fpm.conf
www pool: /nhsla/etc/www.conf
additional configuration files may be added into /etc/php/fpm/conf.d/*.ini
Variable | Description | Default |
---|---|---|
ATATUS_APM_LICENSE_KEY | Provides a licence key to enable the Atatus APM PHP module. Disabled Atatus APM if not set. | |
AWS_HOST_ENVIRONMENT | If exists, then application data will be copied from /app into /app-shared | |
BUILD | A build number from your CICD system, used to form the app version in Atatus. | |
ENVIRONMENT | Name of environment container is deployed to. Mainly used to configure PHP logging and the Atatus release stage | |
MAIL_HOST | Set the SMTP mail host for the system's SSMTP mail relay service | outbound.kube-mail |
MAIL_PORT | Set the SMTP mail port for the system's SSMTP mail relay service | 25 |
REDIS_SESSIONS | Tells PHP FPM to use Redis for a session store. | false |
REDIS_HOST | Combined with above. Sets the redis hostname and port | redis:6379 |
ROLE | Set to CRON or WORKER on the PHP-FPM container to swap the php-fpm service for supercronic. Place cron files in /nhsla/cron | |
SITE_NAME | A name for the site. Mainly used for the Atatus application name | |
SITE_BRANCH | A branch from your code repository. Used to form the app version in Atatus |
The following ports are listening by default. Because of the unprivileged nature of this container all ports must be above 1024.
Port | Service | Use |
---|---|---|
8080/tcp | Openresty | Openresty main web service port |
9145/tcp | nginx-lua-prometheus | Prometheus compatible Openresty metrics exporter |
9253/tcp | php-fpm_exporter | Prometheus compatible metrics exporter |
Module / PHP Version | 7.2 | 7.3 | 7.4 | 8.0 | 8.1 | 8.2 | 8.3 |
---|---|---|---|---|---|---|---|
apcu | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
apcu-bc | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
bcmath | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
calendar | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
ctype | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
curl | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
dom | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
exif | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
ftp | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
gd | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
gettext | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
iconv | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
imagick | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
intl | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
json | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
ldap | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
mbstring | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
memcached | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
mysql | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
mysqli | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
opcache | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
pdo | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
pgsql | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
phar | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
posix | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
redis | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
soap | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
sockets | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
sqlite3 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
tidy | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
xml | ❌ | ❌ | ❌ | ✔️ | ✔️ | ✔️ | ✔️ |
xmlreader | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
xsl | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
zip | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
Each container starts up using Docker's Entryfile directive. This calls the script at /entryfile.sh which will run various scripts in succession.
If the AWS_HOST_ENVIRONMENT
environment variable exists we assume the two images are running inside a Kubernetes Pod together and application code is copied from /app
into a shared emptyDir volume at /app-shared
.
Scripts should be placed into /etc/cont-int.d
in the format 03-script.sh
. 01 and 02 are used for initial setup. You may use the ROLE environment variable to limit scripts to run on different containers.
These images were created with a read only root filesystem in mind, hence the inclusion of three emptyDir volumes in the examples in this repository.
This means that the only writable directories in the container are /nhsla/run, /tmp, and /app-shared. This means that packages cannot be installed in a running container and scope for troubleshooting issues or changing things directly in a Production system is severely limited. Instead the stack should be run locally or other tools such as our logging stack should be consulted.
These images also run as the nhsla
user with a UID/GID of 1000. This means that running services have no access to system directories and have a severely restricted permissions list. This generally shouldn't be an issue but your application may need special attention or slightly rearchitecting to work under these restricted permissions.