Nginx image based on Alpine Linux, with PHP installed in FPM mode.
100K+
Docker Hub - Quay.io - bfren ecosystem
An image for PHP websites: nginx and PHP-FPM in one container, with PHP 7.4 to 8.5. Baïkal, FreshRSS and WordPress are built on it.
It is Nginx with:
php.ini, plus sensible defaults for security, uploads, sessions and OPcache - each one easy to overrideindex.phpThe PHP set-up comes from the PHP image (its bf-php module is copied in when this image is built). There is no SSL support - it is designed to run behind a reverse proxy such as Caddy.
Serve the PHP site in ./site on port 8080:
docker run -d --name web -p 8080:80 -v ./site:/www/public quay.io/bfren/nginx-php:php8.5
Or with Docker Compose:
services:
web:
image: quay.io/bfren/nginx-php:php8.5-9.0
environment:
- BF_PHP_EXT=mbstring pdo_mysql
ports:
- "8080:80"
volumes:
- ./site:/www/public
Open http://localhost:8080. Requests for files that don't exist are sent to /index.php, so front-controller frameworks work without changes.
php.ini and PHP-FPM's www.conf with small JSON filesallow_url_fopen off, phpinfo() disabled, HTTP-only secure session cookies, and the environment hidden from PHP unless you choose otherwiseImages are published to Docker Hub (bfren/nginx-php) and Quay.io (quay.io/bfren/nginx-php) for linux/amd64, linux/arm/v7 and linux/arm64. Tags start with the PHP version.
| Tag | Example | Use when |
|---|---|---|
phpX.Y | php8.5 | You want the latest image for a PHP version |
phpX.Y-MAJOR | php8.5-9 | You want updates, but not breaking changes |
phpX.Y-MINOR | php8.5-9.0 | You want fixes only - recommended for building on |
phpX.Y-VERSION | php8.5-9.0.4 | You want to pin this image exactly |
phpX.Y.Z | php8.5.11 | You want an exact PHP release |
phpX.Y.Z-VERSION | php8.5.11-9.0.4 | You want to pin both |
There is no latest tag - always choose a PHP version.
| PHP | Release | nginx | Alpine | PHP security support ends |
|---|---|---|---|---|
| 8.5 | 8.5.11 | 1.30 | 3.24 | 2029-12-31 |
| 8.4 | 8.4.26 | 1.30 | 3.24 | 2028-12-31 |
| 8.3 | 8.3.35 | 1.30 | 3.24 | 2027-12-31 |
| 8.2 | 8.2.33 | 1.28 | 3.22 | 2026-12-31 |
| 8.1 | 8.1.34 | 1.24 | 3.19 | ended 2025-12-31 |
| 8.0 | 8.0.30 | 1.22 | 3.16 | ended 2023-11-26 |
| 7.4 | 7.4.33 | 1.20 | 3.15 | ended 2022-11-28 |
PHP 8.1 and older no longer receive security fixes, and nor do the Alpine releases they are built on - use them only for legacy sites. Source: php.net
| Port | Purpose |
|---|---|
80 | HTTP - put a reverse proxy in front of it for SSL. |
| Volume | Purpose |
|---|---|
/www | Files in /www/public are served. If you don't mount anything, a "Welcome to Nginx + PHP FPM" page is shown. |
| Variable | Values | Description | Default |
|---|---|---|---|
BF_PHP_EXT | space-separated list | Extensions to install at every start, e.g. mbstring pdo_mysql (see Extensions). | blank |
BF_PHP_ENV | production, development | Which official php.ini to use. | production |
BF_PHP_LOAD_ENV | 0, 1 | Pass every BF_PHP_* variable through to PHP (see Environment variables in PHP). | 0 |
BF_PHP_DIR | path | PHP's configuration folder - set by the image, don't change it. | e.g. /etc/php85 |
BF_PHP_PREFIX | e.g. php85 | Alpine's package prefix for this PHP version - set by the image, don't change it. | e.g. php85 |
Nginx adds the nginx variables (access log, health check URI, …), and Alpine + S6 adds timezone and cron.
When the container starts, Nginx sets up nginx, then this image's initialisation scripts run:
| Script | Does |
|---|---|
20-env.nu | Sets the PHP paths. |
21-ext.nu | Installs the extensions in BF_PHP_EXT. |
22-php-ini.nu | Downloads the php.ini for BF_PHP_ENV if needed, and applies your overrides. |
23-www-conf.nu | Applies your overrides to PHP-FPM's www.conf. |
24-php-env.nu | Passes BF_PHP_* variables to PHP, if BF_PHP_LOAD_ENV=1. |
Then S6 starts nginx and php-fpm. php-fpm checks its configuration (php-fpm -t) first, and if it fails, or PHP-FPM stops unexpectedly, the container stops.
nginx passes requests to PHP-FPM on 127.0.0.1:9000 (the php-fpm upstream). The default site uses index.php as the index page, plus two helper files you can use in your own sites:
| Helper | Description |
|---|---|
php-try-all.conf | Serves a file or folder if it exists, and otherwise sends the request to /index.php. |
php-try-php.conf | Sends .php requests to PHP-FPM (and returns 404 if the file doesn't exist). |
Include them with include helpers/php-try-all.conf; - see Nginx for how sites and helpers work.
php.iniThe official php.ini is downloaded when the image is built (see PHP). At every start, the JSON files in /etc/bf/php.d are read in name order, later files replacing earlier values, and each value is set in php.ini. The image's defaults are:
| File | Settings |
|---|---|
00-php-ini.json | allow_url_fopen = Off, disable_functions = phpinfo, memory_limit = 256M, error_log empty |
01-session.json | Session cookies HTTP-only and secure (HTTPS only), strict mode, lifetime 1 day, name bf-nginx-php-sessid |
02-upload.json | post_max_size and upload_max_filesize 64M |
03-jit.json | OPcache on, with the tracing JIT and a 128M buffer |
To change or add settings, add your own file - e.g. mount ./php.json to /etc/bf/php.d/10-site.json:
{
"memory_limit": "512M",
"upload_max_filesize": "256M",
"post_max_size": "256M"
}
The PHP image's /php-ini.json is not used here - use /etc/bf/php.d instead.
www.conf)The same works for PHP-FPM: at every start, the JSON files in /etc/bf/php-fpm.d are applied to www.conf. PHP-FPM runs as the www user, and the defaults are catch_workers_output = yes, decorate_workers_output = no and clear_env = yes. For example, to allow more PHP processes:
{
"pm.max_children": "20"
}
clear_env = yes means PHP can't see the container's environment variables - so secrets passed to the container aren't readable by every script. To pass some through, name them BF_PHP_… and set BF_PHP_LOAD_ENV=1. Every BF_PHP_* variable is then available to PHP by the same name:
$host = getenv('BF_PHP_DB_HOST');
This includes the image's own BF_PHP_* variables (e.g. BF_PHP_DIR), which are harmless.
BF_PHP_EXT installs extensions as Alpine packages at every start, so the container needs internet access and starts more slowly. That's convenient for trying things out, but for anything you deploy, install them when you build your own image instead (see Building on this image and PHP).
Every hour, a cron job runs PHP's session clean-up as the www user, removing sessions older than session.gc_maxlifetime (1 day by default, or your override).
These come from Nginx. The health check loads http://localhost, so with the default site it checks PHP-FPM too.
bf-nginx-php module| Command | Use |
|---|---|
bf-nginx-php conf test | Test the PHP-FPM configuration. |
bf-nginx-php override get_values | Read and merge the JSON files in a folder (used for php.ini and www.conf). |
bf-nginx-php sessions clean | Remove expired sessions (run hourly). |
bf-nginx-php load-env | Add environment variables to PHP-FPM's www.conf. |
The bf-php module is also available.
FROM quay.io/bfren/nginx-php:php8.5-9.0
COPY ./overlay /
RUN bf-install
overlay/tmp/install:
#!/usr/bin/nu
use bf
use bf-php
# Install my-site
def main [] {
bf-php ext install [mbstring pdo_mysql]
}
Put your site in overlay/www/public (or mount it), nginx configuration in overlay/etc/nginx/sites or custom, and php.ini settings in overlay/etc/bf/php.d. See Nginx for the rest.
./test.nu # builds the PHP 8.5 image and runs /test
./test.nu 8.2 # the same for PHP 8.2
./test.nu --all # every version, in parallel
./run.sh # builds the PHP 8.5 image and opens a shell (port 3000)
/test runs this image's tests alongside the base images' tests, and fails if tests don't execute. As well as the PHP image's tests, they check that php-fpm links to the versioned executable, the session and OPcache extensions are loaded, the PHP-FPM configuration is valid, override files merge in the right order, and BF_PHP_LOAD_ENV writes the right lines to www.conf.
Session cookies are secure by default, so browsers only send them over HTTPS. Use HTTPS (e.g. through your proxy), or for local testing add {"session.cookie_secure": "0"} to a file in /etc/bf/php.d.
PHP-FPM's configuration test failed - often a bad value in a /etc/bf/php-fpm.d file. Run with BF_DEBUG=1 and check docker logs.
The function's extension isn't installed - add it with BF_PHP_EXT or in your own image. Package names are at pkgs.alpinelinux.org.
getenv() returns nothingPHP can't see the container's environment - see Environment variables in PHP.
file_get_contents('https://…') failsallow_url_fopen is off by default. Use curl (BF_PHP_EXT=curl), or turn it on in a /etc/bf/php.d file.
One container per site is simpler to run and back up, and S6 supervises both processes - if either fails, the container stops so Docker can restart it.
Copyright (c) 2020-2026 bfren (unless otherwise stated)
Content type
Image
Digest
sha256:07f1f00d0…
Size
37.4 MB
Last updated
about 3 hours ago
docker pull bfren/nginx-php:php8.5.12-9.0.6