Sign inSign up

bfren/nginx-php

By bfren

•Updated about 15 hours ago

Nginx image based on Alpine Linux, with PHP installed in FPM mode.

Image
0

100K+

bfren/nginx-php repository overview

⁠Nginx + PHP

GitHub release (latest by date) Docker Pulls Docker Image Size GitHub Workflow Status

Docker Hub⁠ - Quay.io⁠ - bfren ecosystem⁠

⁠Introduction

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 and PHP-FPM from Alpine's packages, with the session and OPcache extensions
  • the official php.ini, plus sensible defaults for security, uploads, sessions and OPcache - each one easy to override
  • nginx set up to run PHP, with "pretty" URLs routed to index.php
  • hourly cleaning of old sessions

The 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⁠.

⁠Quickstart

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.

⁠Features

  • 🐘 PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5 - choose with the image tag
  • ⚡ PHP-FPM, with OPcache (and the JIT compiler on PHP 8) turned on
  • ⚙️ Override php.ini and PHP-FPM's www.conf with small JSON files
  • 🔒 Secure defaults: allow_url_fopen off, phpinfo() disabled, HTTP-only secure session cookies, and the environment hidden from PHP unless you choose otherwise
  • 🧹 Expired sessions cleaned every hour
  • 🗜️ Everything from Nginx⁠: compression, helper files, logs, daily reload and a health check

⁠Tags

Images 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.

TagExampleUse when
phpX.Yphp8.5You want the latest image for a PHP version
phpX.Y-MAJORphp8.5-9You want updates, but not breaking changes
phpX.Y-MINORphp8.5-9.0You want fixes only - recommended for building on
phpX.Y-VERSIONphp8.5-9.0.4You want to pin this image exactly
phpX.Y.Zphp8.5.11You want an exact PHP release
phpX.Y.Z-VERSIONphp8.5.11-9.0.4You want to pin both

There is no latest tag - always choose a PHP version.

PHPReleasenginxAlpinePHP security support ends
8.58.5.111.303.242029-12-31
8.48.4.261.303.242028-12-31
8.38.3.351.303.242027-12-31
8.28.2.331.283.222026-12-31
8.18.1.341.243.19ended 2025-12-31
8.08.0.301.223.16ended 2023-11-26
7.47.4.331.203.15ended 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⁠

⁠Configuration

⁠Ports
PortPurpose
80HTTP - put a reverse proxy in front of it for SSL.
⁠Volumes
VolumePurpose
/wwwFiles in /www/public are served. If you don't mount anything, a "Welcome to Nginx + PHP FPM" page is shown.
⁠Environment Variables
VariableValuesDescriptionDefault
BF_PHP_EXTspace-separated listExtensions to install at every start, e.g. mbstring pdo_mysql (see Extensions⁠).blank
BF_PHP_ENVproduction, developmentWhich official php.ini to use.production
BF_PHP_LOAD_ENV0, 1Pass every BF_PHP_* variable through to PHP (see Environment variables in PHP⁠).0
BF_PHP_DIRpathPHP's configuration folder - set by the image, don't change it.e.g. /etc/php85
BF_PHP_PREFIXe.g. php85Alpine'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.

⁠How It Works

⁠Starting

When the container starts, Nginx⁠ sets up nginx, then this image's initialisation scripts run:

ScriptDoes
20-env.nuSets the PHP paths.
21-ext.nuInstalls the extensions in BF_PHP_EXT.
22-php-ini.nuDownloads the php.ini for BF_PHP_ENV if needed, and applies your overrides.
23-www-conf.nuApplies your overrides to PHP-FPM's www.conf.
24-php-env.nuPasses 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 and PHP

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:

HelperDescription
php-try-all.confServes a file or folder if it exists, and otherwise sends the request to /index.php.
php-try-php.confSends .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.ini

The 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:

FileSettings
00-php-ini.jsonallow_url_fopen = Off, disable_functions = phpinfo, memory_limit = 256M, error_log empty
01-session.jsonSession cookies HTTP-only and secure (HTTPS only), strict mode, lifetime 1 day, name bf-nginx-php-sessid
02-upload.jsonpost_max_size and upload_max_filesize 64M
03-jit.jsonOPcache 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.

⁠PHP-FPM settings (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"
}
⁠Environment variables in PHP

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.

⁠Extensions

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⁠).

⁠Sessions

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).

⁠Health check, logs and reloading

These come from Nginx⁠. The health check loads http://localhost, so with the default site it checks PHP-FPM too.

⁠The bf-nginx-php module
CommandUse
bf-nginx-php conf testTest the PHP-FPM configuration.
bf-nginx-php override get_valuesRead and merge the JSON files in a folder (used for php.ini and www.conf).
bf-nginx-php sessions cleanRemove expired sessions (run hourly).
bf-nginx-php load-envAdd environment variables to PHP-FPM's www.conf.

The bf-php module⁠ is also available.

⁠Building on this image

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.

⁠Testing

./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.

⁠Troubleshooting

⁠Logins or sessions don't work over HTTP

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.

⁠The container stops at start-up

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.

⁠"Call to undefined function"

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 nothing

PHP can't see the container's environment - see Environment variables in PHP⁠.

⁠file_get_contents('https://…') fails

allow_url_fopen is off by default. Use curl (BF_PHP_EXT=curl), or turn it on in a /etc/bf/php.d file.

⁠FAQs

⁠Why nginx and PHP-FPM in one container?

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.

⁠Licence

Copyright (c) 2020-2026 bfren⁠ (unless otherwise stated)

Tag summary

Content type

Image

Digest

sha256:07f1f00d0…

Size

37.4 MB

Last updated

about 15 hours ago

docker pull bfren/nginx-php:php8.5.12-9.0.6