# Docker PHP-FPM 8.3 & Nginx 1.26 on Alpine Linux 3.20 [![Docker Pulls](https://img.shields.io/docker/pulls/erseco/alpine-php-webserver.svg)](https://hub.docker.com/r/erseco/alpine-php-webserver/) ![Docker Image Size](https://img.shields.io/docker/image-size/erseco/alpine-php-webserver) ![alpine 3.20](https://img.shields.io/badge/alpine-3.20-brightgreen.svg) ![nginx 1.26.0](https://img.shields.io/badge/nginx-1.26-brightgreen.svg) ![php 8.3](https://img.shields.io/badge/php-8.3-brightgreen.svg) ![License MIT](https://img.shields.io/badge/license-MIT-blue.svg) Example PHP-FPM 8.3 & Nginx 1.26 setup for Docker, build on [Alpine Linux](https://www.alpinelinux.org/). The image is only +/- 25MB large. Repository: https://github.com/erseco/alpine-php-webserver * Built on the lightweight and secure Alpine Linux distribution * Very small Docker image size (+/-25MB) * Uses PHP 8.3 for better performance, lower cpu usage & memory footprint * Multi-arch support: 386, amd64, arm/v6, arm/v7, arm64, ppc64le, s390x * Optimized for 100 concurrent users * Optimized to only use resources when there's traffic (by using PHP-FPM's ondemand PM) * Use of runit instead of supervisord to reduce memory footprint * The servers Nginx, PHP-FPM run under a non-privileged user (nobody) to make it more secure * The logs of all the services are redirected to the output of the Docker container (visible with `docker logs -f `) * Follows the KISS principle (Keep It Simple, Stupid) to make it easy to understand and adjust the image to your needs * Also availabe in Apache flavour: `erseco/alpine-php-webserver:apache` ## Usage Start the Docker container: docker run -p 80:8080 erseco/alpine-php-webserver See the PHP info on http://localhost, or the static html page on http://localhost/test.html Or mount your own code to be served by PHP-FPM & Nginx docker run -p 80:8080 -v ~/my-codebase:/var/www/html erseco/alpine-php-webserver ## Running with Docker Compose Easily serve your local PHP files using Docker Compose. This setup mounts your `./php` directory and binds it to port 8080 on your local machine, allowing for immediate reflection of changes in your PHP files through a web server. It's perfect for local development and testing. ### Docker Compose Configuration Here's a simple `docker-compose.yml` example to get you started: ```yaml version: '3.8' services: webserver: image: erseco/alpine-php-webserver ports: - 8080:8080 volumes: - ./php:/var/www/html restart: unless-stopped ``` - **image**: Uses `erseco/alpine-php-webserver`, optimized for PHP applications. - **ports**: Maps port 8080 from the container to your local machine, accessible at `http://localhost:8080`. - **volumes**: Mounts your local `./php` directory to `/var/www/html` in the container, enabling live updates to your PHP files. - **restart**: Ensures the container automatically restarts unless manually stopped, for better reliability. ### How to Use 1. Save the above `docker-compose.yml` in your project directory. 2. Run `docker-compose up -d` in your terminal, within the same directory. 3. Access your PHP application at `http://localhost:8080`. This method ensures a seamless development process, allowing you to focus on coding rather than setup complexities. ## Adding additional daemons You can add additional daemons (e.g. your own app) to the image by creating runit entries. You only have to write a small shell script which runs your daemon, and runit will keep it up and running for you, restarting it when it crashes, etc. The shell script must be called `run`, must be executable, and is to be placed in the directory `/etc/service/`. Here's an example showing you how a memcached server runit entry can be made. #!/bin/sh ### In memcached.sh (make sure this file is chmod +x): # `chpst -u memcache` runs the given command as the user `memcache`. # If you omit that part, the command will be run as root. exec 2>&1 chpst -u memcache /usr/bin/memcached ### In Dockerfile: RUN mkdir /etc/service/memcached ADD memcached.sh /etc/service/memcached/run Note that the shell script must run the daemon **without letting it daemonize/fork it**. Usually, daemons provide a command line flag or a config file option for that. ## Running scripts during container startup You can set your own scripts during startup, just add your scripts in `/docker-entrypoint-init.d/`. The scripts are run in lexicographic order. All scripts must exit correctly, e.g. with exit code 0. If any script exits with a non-zero exit code, the booting will fail. The following example shows how you can add a startup script. This script simply logs the time of boot to the file /tmp/boottime.txt. #!/bin/sh ### In logtime.sh (make sure this file is chmod +x): date > /tmp/boottime.txt ### In Dockerfile: ADD logtime.sh /docker-entrypoint-init.d/logtime.sh ## Nginx Configuration The Nginx configuration is designed to be flexible and easy to customize. By default, the main configuration file is located at `rootfs/etc/nginx/nginx.conf`. ### Adding Custom Configurations You can add custom configurations in two ways: 1. **Global Configurations**: Place your configuration files in `/etc/nginx/conf.d/`. These configurations are included globally and affect all server blocks. 2. **Server-Specific Configurations**: For configurations specific to a particular server block, place your files in `/etc/nginx/server-conf.d/`. These are included within the server block, allowing for more granular control. ### Example To add a custom configuration, create a `.conf` file in the appropriate directory. For example, to add a server-specific rule, you might create a file named `custom-server.conf` in `/etc/nginx/server-conf.d/` with the following content: ```nginx # Example custom server configuration location /custom { return 200 'Custom server configuration is working!'; add_header Content-Type text/plain; } ``` This setup allows you to easily manage and customize your Nginx configurations without modifying the main `nginx.conf` file. In [rootfs/etc/](rootfs/etc/) you'll find the default configuration files for Nginx, PHP and PHP-FPM. If you want to extend or customize that you can do so by mounting a configuration file in the correct folder; Nginx configuration: docker run -v "`pwd`/nginx-server.conf:/etc/nginx/conf.d/server.conf" erseco/alpine-php-webserver PHP configuration: docker run -v "`pwd`/php-setting.ini:/etc/php8/conf.d/settings.ini" erseco/alpine-php-webserver PHP-FPM configuration: docker run -v "`pwd`/php-fpm-settings.conf:/etc/php8/php-fpm.d/server.conf" erseco/alpine-php-webserver _Note; Because `-v` requires an absolute path I've added `pwd` in the example to return the absolute path to the current directory_ ## Environment variables You can define the next environment variables to change values from NGINX and PHP | Server | Variable Name | Default | description | |--------|-------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | NGINX | nginx_root_directory | /var/www/html | Sets the root directory for the NGINX server, which specifies the location from which files are served. This is the directory where your web application's public files should reside. | | NGINX | client_max_body_size | 2m | Sets the maximum allowed size of the client request body, specified in the “Content-Length” request header field. | | PHP8 | clear_env | no | Clear environment in FPM workers. Prevents arbitrary environment variables from reaching FPM worker processes by clearing the environment in workers before env vars specified in this pool configuration are added. | | PHP8 | allow_url_fopen | On | Enable the URL-aware fopen wrappers that enable accessing URL object like files. Default wrappers are provided for the access of remote files using the ftp or http protocol, some extensions like zlib may register additional wrappers. | | PHP8 | allow_url_include | Off | Allow the use of URL-aware fopen wrappers with the following functions: include(), include_once(), require(), require_once(). | | PHP8 | display_errors | Off | Eetermine whether errors should be printed to the screen as part of the output or if they should be hidden from the user. | | PHP8 | file_uploads | On | Whether or not to allow HTTP file uploads. | | PHP8 | max_execution_time | 0 | Maximum time in seconds a script is allowed to run before it is terminated by the parser. This helps prevent poorly written scripts from tying up the server. The default setting is 30. | | PHP8 | max_input_time | -1 | Maximum time in seconds a script is allowed to parse input data, like POST, GET and file uploads. | | PHP8 | max_input_vars | 1000 | Maximum number of input variables allowed per request and can be used to deter denial of service attacks involving hash collisions on the input variable names. | | PHP8 | memory_limit | 128M | Maximum amount of memory in bytes that a script is allowed to allocate. This helps prevent poorly written scripts for eating up all available memory on a server. Note that to have no memory limit, set this directive to -1. | | PHP8 | post_max_size | 8M | Max size of post data allowed. This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize. Generally speaking, memory_limit should be larger than post_max_size. | | PHP8 | upload_max_filesize | 2M | Maximum size of an uploaded file. | | PHP8 | zlib_output_compression | On | Whether to transparently compress pages. If this option is set to "On" in php.ini or the Apache configuration, pages are compressed if the browser sends an "Accept-Encoding: gzip" or "deflate" header. | | PHP8 | date_timezone | UTC | Sets the PHP timezone configuration (date.timezone) in custom.ini. Accepts standard PHP timezone identifiers (e.g., 'America/New_York', 'Europe/London'). See [PHP timezones](https://www.php.net/manual/en/timezones.php) for valid values. | _Note; Because `-v` requires an absolute path I've added `pwd` in the example to return the absolute path to the current directory_ ## Adding composer If you need [Composer](https://getcomposer.org/) in your project, here's an easy way to add it. ```dockerfile FROM erseco/alpine-php-webserver:latest USER root # Install composer from the official image RUN apk add --no-cache composer USER nobody # Run composer install to install the dependencies RUN composer install --optimize-autoloader --no-interaction --no-progress ``` ### Building with composer If you are building an image with source code in it and dependencies managed by composer then the definition can be improved. The dependencies should be retrieved by the composer but the composer itself (`/usr/bin/composer`) is not necessary to be included in the image. ```Dockerfile FROM composer AS composer # copying the source directory and install the dependencies with composer COPY / /app # run composer install to install the dependencies RUN composer install \ --optimize-autoloader \ --no-interaction \ --no-progress # continue stage build with the desired image and copy the source including the # dependencies downloaded by composer FROM erseco/alpine-php-webserver COPY --chown=nginx --from=composer /app /var/www/html ``` ## Running Commands as Root In certain situations, you might need to run commands as `root` within your Moodle container, for example, to install additional packages. You can do this using the `docker-compose exec` command with the `--user root` option. Here's how: ```bash docker-compose exec --user root alpine-php-webserver sh ```