# Introduction ::hero-video --- src: https://docker-php-public-assets.serversideup.net/docker-demo.mp4 --- :: ## What's "serversideup/php" ::badges :: **serversideup/php** takes the official PHP Docker images and adds everything you need for production: better security, performance optimizations, and a developer experience that just works. ## These images are very different from other PHP Docker Images ::u-page-grid :::u-page-card --- icon: i-features-rocket-square orientation: vertical --- #title Production Ready #description Built to be performant and secure to exist on the wild web. ::: :::u-page-card --- icon: i-features-heart-square orientation: vertical --- #title Native Health Checks #description Be 100% confident your application is actually running. ::: :::u-page-card --- icon: i-features-lightning-square orientation: vertical --- #title High Performance #description Get the easiest experience for fine tuning performance. ::: :::u-page-card --- icon: i-features-stars-square orientation: vertical --- #title Customizable & Flexible #description Environment variables make customizations a breeze. ::: :::u-page-card --- icon: i-features-cloudflare-square orientation: vertical --- #title Native CloudFlare Support #description Get real IP addresses from visitors from trusted proxies. ::: :::u-page-card --- icon: i-features-php-square orientation: vertical --- #title Based on official PHP #description Upgrade from the official PHP docker images with confidence. ::: :::u-page-card --- icon: i-features-frankenphp-square orientation: vertical --- #title FrankenPHP #description Ditch FPM for a modern way of running PHP. Designed for containers from the ground up. ::: :::u-page-card --- icon: i-features-logging-square orientation: vertical --- #title Unified Logging #description All logs are directed to STDOUT & STDERR for centralized output. ::: :::u-page-card --- icon: i-features-heartbeat-square orientation: vertical --- #title FPM + S6 Overlay #description Our FPM-Apache & FPM-NGINX images use this intelligent init system. ::: :: :br ::u-button --- ariaLabel: Read more about the advantages class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: See all advantages size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/these-images-vs-others trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Ready to get started? Ship PHP applications faster with a production-ready setup that includes everything you need out of the box. ::u-button --- ariaLabel: Installation class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Installation size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/installation trailing-icon: i-lucide-arrow-right variant: outline --- :: # Container Basics ## Why even care about containers? If you're new to Docker or containers, you might be wondering why you should containerize your application in the first place. The short answer: **containers let you run your application anywhere** — from your laptop to any cloud provider — with zero changes. Key benefits of containerization: - **Consistency** - Your app runs the same on Mac, Windows, Linux, and production - **Confidence** - Infrastructure as code means easier testing and rollbacks - **Freedom** - No vendor lock-in; migrate hosts with minimal effort - **Simplicity** - Scaling from 1 to 100 containers is straightforward There are some important terms to understand when working with containers: | Term | Definition | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Container | A running instance of an image. | | Image | A template for a container to start with (ie. `serversideup/php:8.5-frankenphp`). | | Tag | A specific version of an image (ie. `8.5-frankenphp`). | | Registry | A repository of images. This is where users can pull images from to start a container. This can be places like [Docker Hub](https://hub.docker.com/r/serversideup/php){rel="nofollow"} or [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php){rel="nofollow"}. | | Volume | A directory on your host machine that is mounted into a container. This allows you to share files between your host machine and the container. | | Port | Ports are virtual numbers organizing network data traffic, directing it to the correct application on a device. If you want to expose traffic to work with `http://localhost`, you would map port `80` on your host machine to port `8080` on the container. | | Environment Variable | A variable that is set in the container's environment. This allows you to configure the container's behavior. | ## What are containers? Containers are isolated environments that can run on any host. They are a great way to package your application and all of its dependencies into a single unit that can be easily deployed to any environment. ![Container Layers with Laravel Application](https://serversideup.net/open-source/docker-php/images/docs/container-layers.png) ### Why containerize at all? Going through the efforts of containerizing your application gives you one extremely powerful capability: > 👉 **You can run your application anywhere.** Once you're at this level, you unlock a ton of new possibilities: - **Your application runs the same across Mac, Windows, and Linux** - no more "it works on my machine". Run 100% of your application on any operating system. All they need is Docker installed. - **Ship with more confidence** - all infrastructure configurations are *centrally managed* in Git, so you can change, test and rollback with ease - **No more vendor lock-in** - if a host raises their prices on you, you can migrate with very little effort - **Scaling is a breeze** - once you get your application in one container, it's so easy to scale up to any number of containers - **Improved security** - containerized apps are more secure than traditional apps because they are isolated from the host - **Better uptime** - if something fails during deployment, you can roll back your application to a previous version The crazier part is you don't need a Platform as an expensive Service (PaaS) to get all these benefits. Everything can be done with 100% free and open source tools. ## Docker vs. Containers When you're first learning about containers, you'll likely hear the terms "Docker" and "containers" used interchangeably. While they are related, they are not the same thing. [Docker](https://www.docker.com/){rel="nofollow"} is an open source utility for building, shipping, and running applications in containers. It's one of the most popular tools for containerization. Although you may see us reference things as "Docker containers", we're actually referring to the broader concept of containers because containers follow a standard known as the [OCI (Open Container Initiative)](https://opencontainers.org/){rel="nofollow"}. This means Docker containers can run on any container orchestrator that supports the OCI standard (like Kubernetes, Docker Swarm, Docker Compose, HashiCorp Nomad, etc.). So although we're going to show you best practices with Docker, this means you'll maintain your freedom and flexibility to choose how you want to run your containers. ## How containers work If you followed our [installation guide](https://serversideup.net/open-source/docker-php/docs/getting-started/installation), you've already run containers and seen them in action. Let's break down what actually happened when you ran `docker compose up`. ### Images vs Containers Think of an **image** as a blueprint and a **container** as the actual running instance of that blueprint. When you specified `image: serversideup/php:8.5-fpm-nginx` in your `compose.yml`, you told Docker to: 1. Download the `serversideup/php:8.5-fpm-nginx` image (the blueprint) 2. Create a container from that image (the running instance) 3. Start the container with your specified configuration You can create multiple containers from the same image — just like you can build multiple houses from the same blueprint. Each container runs independently with its own isolated filesystem and processes. ### Understanding image layers Container images are built in layers, like a stack of pancakes. Each layer adds something new: the operating system, PHP, web servers, and configurations. When you pull an image, Docker downloads only the layers you don't already have, making updates incredibly efficient. This is why switching from PHP 8.3 to 8.4 in the installation guide was so fast — most of the layers were already on your machine, and Docker only downloaded the differences. ::tip Image tags like `8.3-fpm-nginx` and `8.4-frankenphp` aren't just version numbers — they describe the entire stack that's included in that image. The tag tells you the PHP version and which variation (web server stack) you're getting. :: ## Key concepts ### Service names A **service** in Docker Compose is a named container definition. Think of it as a label for a specific part of your application — like your web server, database, or cache. Each service runs in its own container and can be managed independently. In your `compose.yml`, when you define a service called `php`, you're telling Docker Compose "this is my PHP application server." You can name it anything you want — we use `php` to keep it simple, but `web`, `app`, or `backend` would work just as well. ```yml [compose.yml] {2} services: php: image: serversideup/php:8.5-fpm-nginx ports: - 80:8080 ``` Whenever you run commands, you'll need to reference the service name you gave it. ```bash [Terminal] docker compose run php php -v ``` If you named your service `app`, you would run the following command: ```bash [Terminal] docker compose run app php -v ``` ### Volumes: Sharing files with containers When you added this to your `compose.yml`: ```yml volumes: - ./:/var/www/html ``` You created a **bind mount** that connects your local project directory to the container's `/var/www/html` directory. This means: - Changes you make on your computer are instantly visible inside the container - The container serves your actual project files, not a copy - When the container stops, your files remain on your computer This is perfect for development because you can edit files with your favorite editor and see changes immediately without rebuilding the container. ::note In production, you'll typically build your application files directly into the image instead of using volumes. We cover this in our [packaging guide](https://serversideup.net/open-source/docker-php/docs/deployment-and-production/packaging-your-app-for-deployment). :: ### Ports: Accessing your container The `ports` configuration maps ports between your computer and the container: ```yml ports: - 80:8080 ``` This means "take port 8080 inside the container and make it available on port 80 on my computer." When you visited `http://localhost` in your browser, you were actually connecting to port 80 on your computer, which Docker forwarded to port 8080 inside the container where NGINX or FrankenPHP was listening. ::warning Only one service can use a port at a time. If you get a "port already in use" error, another service on your computer is already using that port. Try using a different port like `8000:8080`. :: ### Environment variables: Configuring your container Environment variables let you configure your container without modifying the image. When you set: ```yml environment: PHP_UPLOAD_MAX_FILE_SIZE: "500M" PHP_OPCACHE_ENABLE: "1" ``` You're telling our PHP images to adjust PHP's configuration. Our images read these variables at startup and automatically configure PHP accordingly. This means the same image can be configured differently for development, staging, and production — just by changing environment variables. ::tip --- to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification --- Check out our [Environment Variable Specification](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) to see all available configuration options. :: ## The container lifecycle Understanding the lifecycle helps you work more effectively with containers: 1. **Create & Start** - `docker compose up` creates and starts containers 2. **Running** - Your application is active and serving requests 3. **Stop** - `docker compose down` stops containers (but keeps configurations) 4. **Remove** - Containers are deleted (but images remain for faster restarts) Important to know: - Stopping a container doesn't always delete it - Restarting is fast because the image is already downloaded - Any data stored inside the container (not in volumes) is lost when the container is removed - Your images remain on your machine until you explicitly remove them ## Common Docker commands Here are the essential commands you'll use regularly: ```bash [Terminal] # Start containers (creates if they don't exist) docker compose up # Start in background (detached mode) docker compose up -d # Stop and remove containers docker compose down # View running containers docker compose ps # View container logs docker compose logs # Follow logs in real-time docker compose logs -f # Execute a command in a running container docker compose exec php php -v # Rebuild containers after image changes docker compose up --build # Remove images to force fresh download docker compose down --rmi all ``` ::tip The `-f` flag in `docker compose logs -f` means "follow" — it keeps showing new log entries as they happen. Press `` + `` to stop following. :: ## What's next? Now that you understand the fundamentals of containers, let's create your first containerized PHP project. ::u-button --- ariaLabel: Create your first Docker PHP project class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Create your first Docker PHP project size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/installation trailing-icon: i-lucide-arrow-right variant: outline --- :: # Installation ::lead-p `serversideup/php` is compatible with any container orchestrator that supports Docker images (Kubernetes, Docker Swarm, Docker Compose, HashiCorp Nomad, etc.). All images are hosted on [DockerHub](https://hub.docker.com/r/serversideup/php){rel="nofollow"} and [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php){rel="nofollow"} for free. Containers default to running Debian, but Alpine images are also available. :: ## Quick Start ::tip --- to: https://serversideup.net/open-source/docker-php/docs/getting-started/container-basics --- New to containers? Check out our [Container Basics guide](https://serversideup.net/open-source/docker-php/docs/getting-started/container-basics) for a beginner-friendly introduction to Docker and containerization. :: In order to run containers, we need a container engine installed such as Docker. You can follow [Docker's installation guide](https://docs.docker.com/get-started/get-docker/){rel="nofollow"} to get started. Confirm Docker is working by running these commands in your terminal: ```bash [Terminal] # Check Docker version docker --version # Check Docker Compose version docker compose version ``` If you see version numbers after running the commands, you're ready to go! ### Create a new sample project Let's create a new sample project to test our Docker setup. Open up your terminal and run the following commands: ```bash [Terminal] # Create a project directory with a public directory mkdir -p my-php-project/public # Change directory to our project cd my-php-project ``` Once we're in our project directory, we will want to create two files: 1. `public/index.php` - The file that will be served when someone visits our site 2. `compose.yml` - The place where we will put our configuration files The content of these files should look like: ::code-tree{default-value="public/index.php"} ```yml [compose.yml] services: php: # Choose our PHP version and variation image: serversideup/php:8.3-fpm-nginx # Expose localhost:80 to NGINX's port 8080 ports: - 80:8080 # Mount current directory to /var/www/html volumes: - ./:/var/www/html # PHP settings that we can override environment: PHP_UPLOAD_MAX_FILE_SIZE: "250M" PHP_OPCACHE_ENABLE: "0" ``` ```php [public/index.php] ``` :: Save these files and ensure they are in the exact structure above. ### Bringing up your PHP app From your project root directory, run the following command to start your PHP app: ::warning Make sure you are running the following commands from your **project root directory** (`my-php-project`). If you do not have `compose.yml` in the same directory as you run this command, the command will not work. Also, make sure you don't have any other containers or services that are currently running on port 80. If you do, you will need to stop them before running the following command. :: ::steps{level="4"} #### Start the PHP container ```bash [Terminal] docker compose up ``` You'll see the logs appear in your terminal. **Keep your terminal open** as we'll need it to control the container. ![Terminal output showing Docker Compose successfully starting a PHP container with nginx logs](https://serversideup.net/open-source/docker-php/images/docs/running-php-container.png) #### Viewing your PHP app To view your PHP app, open your browser and navigate to `http://localhost`. You should see the PHP info page showing PHP `8.3` with the `fpm-nginx` variation: ![PHP Info Page](https://serversideup.net/open-source/docker-php/images/docs/php-info.png) You can see the PHP version is in the upper left corner of the page. But what's also cool is you can see the settings we configured in the `compose.yml` file are being applied. Look for these values in the PHP info page: - `upload_max_filesize` - This should show `250M` - `opcache.enable` - This should show `Off` :: ### Making changes ::caution To have our changes take effect, we must restart the containers. :: Let's make some upgrades to our PHP app: 1. Let's upgrade to PHP 8.4 2. Let's use FrankenPHP instead of FPM-NGINX 3. Turn on OPCache 4. Increase the upload limit to 500M To do this, we need to need to: ::steps{level="4"} #### Stop the container **Press `` + `` on the original terminal window** or you can run the following command *in a new terminal window from your project root directory*: ```bash [Terminal] docker compose down ``` #### Making changes to your PHP app Make the updates below to your `compose.yml` file: ```yml [compose.yml] {3-4,10-13} services: php: # Change image to 8.4 with FrankenPHP image: serversideup/php:8.4-frankenphp ports: - 80:8080 volumes: - ./:/var/www/html environment: # Increase the upload limit to 500M PHP_UPLOAD_MAX_FILE_SIZE: "500M" # Turn OPCache on PHP_OPCACHE_ENABLE: "1" ``` #### Bring the container up again ```bash [Terminal] docker compose up ``` You'll see the logs appear in your terminal. **Keep your terminal open** as we'll need it to control the container. #### Refresh your browser Check `http://localhost` again and you should see the changes we made: ![PHP Info Page with Changes to PHP version and variation](https://serversideup.net/open-source/docker-php/images/docs/php-info-changes-version-and-server.png) Holy smokes! We've upgraded to PHP 8.4 and you're using FrankenPHP! You can also find the changes to the PHP settings have been applied. ![PHP Info Page with Changes to server options](https://serversideup.net/open-source/docker-php/images/docs/php-info-with-changes-options.png) :: ## You've got this đŸ’Ē You've successfully created your first PHP app with Docker. Better yet, you've seen the power of serversideup/php where it's easy to change your PHP version and variation by changing a single line in your configuration file. If you're curious what the whole process looks like from Development to Production, check out out guide below. ::u-button --- ariaLabel: Learn how to deploy your PHP app from Development to Production class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn how to deploy your PHP app from Development to Production size: md to: https://serversideup.net/open-source/docker-php/docs/deployment-and-production/development-to-production trailing-icon: i-lucide-arrow-right variant: outline --- :: # These Images vs Others ## The Problem with Traditional PHP Deployment If you've ever deployed a PHP application to production, you've probably experienced one (or all) of these frustrations: - **"It works on my machine"** - Your local environment doesn't match production, leading to mysterious bugs that only appear after deployment - **Configuration Hell** - Spending hours tweaking `php.ini`, `www.conf`, and web server configs across multiple servers - **Security Vulnerabilities** - Running as root, outdated extensions, or misconfigured permissions exposing your application to attacks - **Manual Server Management** - SSH-ing into servers to update PHP, install extensions, or troubleshoot issues - **Inconsistent Environments** - Each server is slightly different, making debugging and scaling a nightmare - **Framework-Specific Tweaks** - Researching and applying dozens of optimizations for Laravel or WordPress performance You're not alone. These are the exact problems that led us to create `serversideup/php`. ## How serversideup/php Solves These Problems `serversideup/php` is built on the official PHP images but adds everything needed for real-world production use: - ✅ **Works Identically Everywhere** - Same container runs on your laptop, CI/CD, and production - ✅ **Zero Configuration Required** - Production-ready defaults with simple environment variable customization - ✅ **Secure by Default** - Runs as unprivileged user, hardened for the open internet - ✅ **Batteries Included** - Composer, common extensions, and helpful utilities pre-installed - ✅ **Framework Optimized** - Pre-configured for Laravel and WordPress best practices - ✅ **Modern Architecture** - FrankenPHP, S6 Overlay, native health checks, and multi-process support ::note **Trusted by the Community** - Over 1 million Docker image pulls and actively used by Laravel and PHP developers worldwide. :: ## Feature Comparison See how `serversideup/php` stacks up against other PHP deployment options: | Feature | **Traditional Server** | **Official PHP Images** | **serversideup/php** | | --------------------------------- | ---------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Consistent environments | ❌ | ✅ | ✅ | | Easy to scale | ❌ | ✅ | ✅ | | Base OS Options | Manual Setup | Debian, Alpine | Debian, Alpine | | PHP Version Management | Manual Updates | Easy Upgrades | Easy Upgrades | | Multi-arch support | ❌ | ✅ | ✅ | | Production-ready defaults | âš ī¸ Manual | ❌ | ✅ | | Runs as non-root user | âš ī¸ Manual | ❌ | ✅ | | Variable-first configuration | ❌ | ❌ | ✅ | | Includes `composer` | âš ī¸ Manual | ❌ | ✅ | | Includes `install-php-extensions` | ❌ | ❌ | ✅ | | Built-in security hardening | âš ī¸ Manual | ❌ | ✅ | | Laravel & WordPress optimizations | âš ī¸ Manual | ❌ | ✅ | | NGINX + FPM variation | âš ī¸ Manual | ❌ | ✅ | | FrankenPHP support | ❌ | ❌ | ✅ | | Native health checks | âš ī¸ Manual | ❌ | ✅ | | S6 Overlay init system | ❌ | ❌ | ✅ | | Published Registries | N/A | DockerHub | [DockerHub](https://hub.docker.com/r/serversideup/php){rel="nofollow"} + [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php){rel="nofollow"} | ## Key Advantages Explained ### Security First: Unprivileged by Default ::caution Running containers as `root` in production is a critical security vulnerability. If your application is compromised, an attacker gains root access to your container which could lead to a full system compromise. :: Our images run as the `www-data` user by default, following the principle of least privilege. This means: - **Limited Blast Radius** - If your application is compromised, don't have root privileges - **Kubernetes Compatible** - Many Kubernetes clusters require non-root containers by policy - **Production Best Practice** - Aligns with NIST and CIS security benchmarks We also include additional security hardening: - Disabled dangerous PHP functions by default (but you control them) - Proper file permissions out of the box - CloudFlare trusted proxy support for accurate IP logging - Regular security updates from official PHP base images ### Performance Optimized Every image includes production-tuned defaults based on real-world PHP applications: **OPcache Configuration** - Pre-configured for optimal memory usage and caching strategy - Easily toggle between development and production modes - Smart defaults that work for most applications **Process Management** - PHP-FPM tuned for typical low resource usage for Laravel/WordPress workloads - S6 Overlay for intelligent process supervision - Graceful shutdown handling for zero-downtime deployments **Modern Options** - FrankenPHP support for incredible performance gains (2-3x faster than FPM in many claims made by developers) - HTTP/2 and HTTP/3 ready configurations ::tip Need to customize performance settings? Just set an environment variable like `PHP_OPCACHE_ENABLE=1` or `PHP_MEMORY_LIMIT=512M`. No config files needed. :: ### Developer Experience: Variable-First Configuration Stop editing config files. Stop rebuilding images for simple changes. Just set environment variables: ```yml [compose.yml] {5-9,11-12} services: php: image: serversideup/php:8.5-fpm-nginx environment: # Change any PHP setting with environment variables PHP_MEMORY_LIMIT: "512M" PHP_UPLOAD_MAX_FILE_SIZE: "100M" PHP_MAX_EXECUTION_TIME: "180" PHP_OPCACHE_ENABLE: "1" # Run Migrations, Storage Link, Caching, and more AUTORUN_ENABLED: "true" ``` No Dockerfile modifications. No config file mounting. No image rebuilds. Just simple environment variables with production-ready defaults. ::u-button --- ariaLabel: View all environment variables class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: View all environment variables size: md to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification trailing-icon: i-lucide-arrow-right variant: outline --- :: ### Production-Ready Out of the Box Our images are built for the wild internet: **Security Hardening** - Unprivileged user execution - Disabled dangerous PHP functions (you control which ones) - Proper file permissions and ownership - Regular security updates **Monitoring & Observability** - Built-in health check endpoints - All logs to STDOUT/STDERR for centralized logging - Compatible with Prometheus, DataDog, and other monitoring tools **Deployment Features** - Zero-downtime deployment support - Graceful shutdown handling - Queue worker and scheduler support for Laravel - Automatic migration running on container start (optional) **Infrastructure as Code** - Version your entire PHP stack in a `compose.yml` file - Reproduce every environment with 100% consistency - Easy rollbacks to previous versions ### Framework Optimized for Laravel & WordPress We've done the hard work of optimizing for PHP's most popular frameworks: **Laravel Automations** ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-nginx environment: # Run migrations, storage link, caching, and more AUTORUN_ENABLED: "true" ``` ::u-button --- ariaLabel: Learn More About Laravel Automations class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn More About Laravel Automations size: md to: https://serversideup.net/open-source/docker-php/docs/framework-guides/laravel/automations trailing-icon: i-lucide-arrow-right variant: outline --- :: **WordPress Optimizations** - Pre-installed PHP extensions WordPress needs - Optimized PHP settings for WordPress performance - Support for popular WordPress hosting patterns **All Frameworks Welcome** While we optimize for Laravel and WordPress, our images work great with: - Symfony - CodeIgniter - Drupal - Joomla - Custom PHP applications ### Batteries Included Unlike the official PHP images, we include tools you'll actually use: **Composer** - Pre-installed and ready to use - No need to install it in every Dockerfile - Supports Composer v2 for lightning-fast installs **install-php-extensions** - The popular [mlocati/docker-php-extension-installer](https://github.com/mlocati/docker-php-extension-installer){rel="nofollow"} included - Install any PHP extension with a single command - Handles all dependencies automatically ```dockerfile [Dockerfile] FROM serversideup/php:8.5-cli # Switch to root to install extensions USER root # Install any PHP extension easily RUN install-php-extensions bcmath imagick mongodb # Switch back to unprivileged user USER www-data ``` **Modern Init System** - S6 Overlay for our FPM-Apache and FPM-NGINX variations - Proper process supervision in containers - Better than Supervisor for containerized workloads - Graceful handling of signals for zero-downtime deployments **Multiple Variations** Choose the right tool for your use case: - [`cli`](https://serversideup.net/open-source/docker-php/docs/image-variations/cli) - Command-line scripts, Composer, CI/CD - [`fpm`](https://serversideup.net/open-source/docker-php/docs/image-variations/fpm) - Just PHP-FPM (bring your own web server) - [`fpm-nginx`](https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-nginx) - PHP-FPM + NGINX (most popular) - [`fpm-apache`](https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-apache) - PHP-FPM + Apache - [`frankenphp`](https://serversideup.net/open-source/docker-php/docs/image-variations/frankenphp) - Modern, incredibly fast (2-3x FPM performance) ## Real-World Impact Don't just take our word for it. Here's what developers are experiencing: ::note **By the Numbers** - **1,000,000+** Docker image pulls - **2,000+** GitHub stars - **Active Community** with regular updates and contributions - **Production-Proven** across startups to enterprise applications :: **Time Savings** - **Minutes vs Hours** - Go from zero to production-ready PHP in minutes, not hours of server configuration - **Consistent Deployments** - Eliminate "works on my machine" debugging sessions - **Pre-configured** - Stop researching optimal PHP settings for Laravel **Better Security** - **Hardened by Default** - Security best practices built-in, not bolted on - **Regular Updates** - Based on official PHP images with security patches - **Audit Trail** - Infrastructure as code means every change is tracked **Happier Developers** - **Simple Configuration** - Environment variables instead of config file archaeology - **Modern Tools** - FrankenPHP, native health checks, and container-native features - **Community Support** - Active community and comprehensive documentation ## Making the Switch ### From Traditional Servers (LAMP/LEMP) If you're currently managing PHP on traditional servers, the switch to containers might seem daunting, but it's easier than you think: **Benefits You'll Gain** - Identical environments from development to production - Scale horizontally by adding more containers - Roll back bad deployments in seconds - Version control your entire infrastructure See our quick start guide to run your first PHP container. ::u-button --- ariaLabel: Quick Start Guide class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Quick Start Guide size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/installation trailing-icon: i-lucide-arrow-right variant: outline --- :: ### From Official PHP Images Already using Docker with official PHP images? Switching is trivially easy. ::u-button --- ariaLabel: Official PHP Migration Guide class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Official PHP Migration Guide size: md to: https://serversideup.net/open-source/docker-php/docs/guide/migrating-from-official-php-images trailing-icon: i-lucide-arrow-right variant: outline --- :: ### From Other Docker Images Switching from Bitnami, custom images, or other PHP Docker images is straightforward: **Key Differences to Note** - We run as `www-data` (UID 33) by default, not root - Configuration via environment variables, not config file mounts - Web root is `/var/www/html` by default - All variations expose port `8080` (unprivileged port) **Migration Strategy** 1. Review our [environment variable specification](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) 2. Map your current config to environment variables 3. Test in development first 4. Switch image tag in production ::warning Always test in a development or staging environment first to ensure your application works correctly with the new image. :: ## Ready to Get Started? You're just minutes away from a better PHP deployment experience. ::steps ### Choose Your Path **Quick Start (New Projects)** Follow our installation guide to create your first PHP app with Docker in under 5 minutes. :::u-button --- ariaLabel: Quick Start Guide class: font-bold text-white bg-blue-600 hover:bg-blue-700 color: primary label: Quick Start Guide size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/installation trailing-icon: i-lucide-rocket variant: solid --- ::: **Migration Guide (Existing Apps)** Already have a PHP application? Learn how to containerize it with serversideup/php. :::u-button --- ariaLabel: Migration Guides class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Migration Guides size: md to: https://serversideup.net/open-source/docker-php/docs/guide/migrating-from-official-php-images trailing-icon: i-lucide-arrow-right variant: outline --- ::: ### Choose an Image Variation Not sure which image variation is right for you? We have a guide for that. :::u-button --- ariaLabel: Choosing an Image class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Choosing an Image size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/choosing-an-image trailing-icon: i-lucide-arrow-right variant: outline --- ::: :: ## Questions? We're here to help! Check out these resources: - 📚 [Full Documentation](https://serversideup.net/open-source/docker-php/docs) - Comprehensive guides and references - đŸ’Ŧ [GitHub Discussions](https://github.com/serversideup/docker-php/discussions){rel="nofollow"} - Ask questions and share experiences - 🐛 [GitHub Issues](https://github.com/serversideup/docker-php/issues){rel="nofollow"} - Report bugs or request features - đŸ—Ŗī¸ [Discord](https://serversideup.net/discord){rel="nofollow"} - Join our community and get help from the team and other developers. ::tip Join our community! Star us on [GitHub](https://github.com/serversideup/docker-php){rel="nofollow"} and follow updates. :: # Choosing an Image ::lead-p Choosing an image might be a little intimidating because there are so many options, but this ultimately gives you a ton of flexibility. This guide will help simplify your decision making process on choosing the right image. :: ## Registry: Where images are located Before diving into the different options available, let's understand where these images come from. The source code is [hosted on GitHub](https://github.com/serversideup/docker-php){rel="nofollow"} with an automated process that builds and uploads images to container registries: 1. [Docker Hub](https://hub.docker.com/r/serversideup/php){rel="nofollow"} 2. [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php){rel="nofollow"} To use these images, you need to specify a certain "tag" for the image you want. For example, the `8.4` version of the `fpm-nginx` variation is tagged as: ```bash serversideup/php:8.4-fpm-nginx ``` This image gives you the latest stable patch version of PHP 8.4 running the `fpm-nginx` variation. Since no registry is specified, it defaults to Docker Hub. When selecting an image, you'll need to decide on: 1. PHP version 2. PHP variation 3. Base operating system 4. serversideup/php version (for production environments) ## Our most popular images All images are intelligently tagged with the PHP version and variation, allowing you to easily select the right image for your use case. If you don't specify a variation, it defaults to `cli` and the latest supported Debian release for that variation. Our most popular tags include: | âš™ī¸ Variation | 🚀 Version | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cli | **Debian Based** [![serversideup/php:8.5-cli](https://img.shields.io/docker/image-size/serversideup/php/8.5-cli?label=serversideup%2Fphp%3A8.5-cli){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.5-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.4-cli](https://img.shields.io/docker/image-size/serversideup/php/8.4-cli?label=serversideup%2Fphp%3A8.4-cli){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.3-cli](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli?label=serversideup%2Fphp%3A8.3-cli){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli?label=serversideup%2Fphp%3A8.2-cli){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli?label=serversideup%2Fphp%3A8.1-cli){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli?label=serversideup%2Fphp%3A8.0-cli){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli?label=serversideup%2Fphp%3A7.4-cli){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli\&page=1\&ordering=-name){rel="nofollow"}:br**Alpine Based** [![serversideup/php:8.5-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.5-cli-alpine?label=serversideup%2Fphp%3A8.5-cli-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.5-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-cli-alpine?label=serversideup%2Fphp%3A8.4-cli-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.3-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli-alpine?label=serversideup%2Fphp%3A8.3-cli-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.2-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli-alpine?label=serversideup%2Fphp%3A8.2-cli-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.1-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli-alpine?label=serversideup%2Fphp%3A8.1-cli-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.0-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli-alpine?label=serversideup%2Fphp%3A8.0-cli-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:7.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli-alpine?label=serversideup%2Fphp%3A7.4-cli-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} | | fpm | **Debian Based** [![serversideup/php:8.5-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.5-fpm?label=serversideup%2Fphp%3A8.5-fpm){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.5-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm?label=serversideup%2Fphp%3A8.4-fpm){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.3-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm?label=serversideup%2Fphp%3A8.3-fpm){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm?label=serversideup%2Fphp%3A8.2-fpm){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm?label=serversideup%2Fphp%3A8.1-fpm){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm?label=serversideup%2Fphp%3A8.0-fpm){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm?label=serversideup%2Fphp%3A7.4-fpm){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm\&page=1\&ordering=-name){rel="nofollow"}:br**Alpine Based** [![serversideup/php:8.5-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.5-fpm-alpine?label=serversideup%2Fphp%3A8.5-fpm-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.5-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-alpine?label=serversideup%2Fphp%3A8.4-fpm-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.3-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-alpine?label=serversideup%2Fphp%3A8.3-fpm-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.2-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-alpine?label=serversideup%2Fphp%3A8.2-fpm-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.1-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-alpine?label=serversideup%2Fphp%3A8.1-fpm-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.0-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-alpine?label=serversideup%2Fphp%3A8.0-fpm-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:7.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-alpine?label=serversideup%2Fphp%3A7.4-fpm-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} | | fpm-apache | **Debian Based** [![serversideup/php:8.5-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.5-fpm-apache?label=serversideup%2Fphp%3A8.5-fpm-apache){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.5-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-apache?label=serversideup%2Fphp%3A8.4-fpm-apache){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.3-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-apache?label=serversideup%2Fphp%3A8.3-fpm-apache){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-apache?label=serversideup%2Fphp%3A8.2-fpm-apache){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-apache?label=serversideup%2Fphp%3A8.1-fpm-apache){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-apache?label=serversideup%2Fphp%3A8.0-fpm-apache){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-apache?label=serversideup%2Fphp%3A7.4-fpm-apache){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} | | fpm-nginx | **Debian Based** [![serversideup/php:8.5-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.5-fpm-nginx?label=serversideup%2Fphp%3A8.5-fpm-nginx){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.5-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx?label=serversideup%2Fphp%3A8.4-fpm-nginx){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.3-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx?label=serversideup%2Fphp%3A8.3-fpm-nginx){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx?label=serversideup%2Fphp%3A8.2-fpm-nginx){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx?label=serversideup%2Fphp%3A8.1-fpm-nginx){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx?label=serversideup%2Fphp%3A8.0-fpm-nginx){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx?label=serversideup%2Fphp%3A7.4-fpm-nginx){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"}:br**Alpine Based** [![serversideup/php:8.5-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.5-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.5-fpm-nginx-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.5-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.4-fpm-nginx-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.3-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.3-fpm-nginx-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.2-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.2-fpm-nginx-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.1-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.1-fpm-nginx-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.0-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.0-fpm-nginx-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:7.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A7.4-fpm-nginx-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} | | frankenphp | **Debian Based** [![serversideup/php:8.5-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.5-frankenphp?label=serversideup%2Fphp%3A8.5-frankenphp){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.5-frankenphp\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.4-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp?label=serversideup%2Fphp%3A8.4-frankenphp){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.3-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp?label=serversideup%2Fphp%3A8.3-frankenphp){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp\&page=1\&ordering=-name){rel="nofollow"} :br**Alpine Based** :br âš ī¸ Some users are experiencing [known performance issues](https://frankenphp.dev/docs/known-issues/){rel="nofollow"} with FrankenPHP on Alpine. Consider using the Debian version. :br [![serversideup/php:8.5-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.5-frankenphp-alpine?label=serversideup%2Fphp%3A8.5-frankenphp-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.5-frankenphp-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.4-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp-alpine?label=serversideup%2Fphp%3A8.4-frankenphp-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php:8.3-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp-alpine?label=serversideup%2Fphp%3A8.3-frankenphp-alpine){.h-5.w-auto :zoom='false'}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp-alpine\&page=1\&ordering=-name){rel="nofollow"} | | unit (deprecated) | NGINX stopped supporting NGINX Unit it has been removed from our project. [Learn more →](https://serversideup.net/open-source/docker-php/docs/image-variations/unit/) | ## The full tag schema Our tagging process is quite flexible, and it can become quite complex, but don't worry -- we've spent a ton of time applying sensible defaults to give you the best experience. The full tag schema is as follows: ```jinja {{registry-url}}/serversideup/php:{{release-prefix}}-{{php-version}}-{{variation-name}}-{{operating-system}}-v{{github-release-version}} ``` Here's what each part means: | Option | Default | Other Options | | -------------------------------------------------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `{{registry-url}}` :br Which registry to pull images from. | `''` :br (Docker Hub) | `ghcr.io` (GitHub Packages) | | `{{release-prefix}}` :br The prefix of the release. | `''` (stable) | `beta` (beta releases) | | `{{php-version}}` :br The version of PHP to use. | Latest stable PHP minor version (ie. `8.5`) | `8.4` :br `8.3` :br `8.2` :br `8.1` :br `8.0` :br `7.4` :br (you can also specify the full version number, ie. `8.4.1`) | | `{{variation-name}}` :br The name of the variation to use. | `cli` | `fpm` :br `fpm-apache` :br `fpm-nginx` :br `frankenphp` :br `unit` (deprecated) | | `{{operating-system}}` :br The operating system to use. | `debian` | `alpine` :br `bullseye` :br `bookworm` :br `trixie` | | `{{github-release-version}}` :br The version of the GitHub release to use. | (latest stable release) | See our [GitHub Releases](https://github.com/serversideup/docker-php/releases){rel="nofollow"} for specific versions. | ::note{title="Floating vs. version-pinned tags"} Including `{{github-release-version}}` (e.g. `8.4-fpm-nginx-v4.3.5`) creates a **version-pinned tag** that is written once and never updated. Omitting it (e.g. `8.4-fpm-nginx`) gives you a **floating tag** that we rebuild weekly with the latest security patches. The right choice depends on how you balance reproducibility against staying current — see [How our releases work](https://serversideup.net/open-source/docker-php/docs/getting-started/upgrade-guide#how-our-releases-work) and [Choosing your update strategy](https://serversideup.net/open-source/docker-php/docs/getting-started/upgrade-guide#choosing-your-update-strategy) in the upgrade guide. :: ## PHP version There are many factors to consider when choosing the right PHP version. Best practices include: 1. Choose the latest PHP version (as your dependencies allow) 2. Choose a PHP version that is receiving [active and security support by PHP](https://www.php.net/supported-versions.php){rel="nofollow"} If you need help understanding what's new in each PHP version, check out [the official PHP changelog](https://www.php.net/ChangeLog-8.php){rel="nofollow"}. This will give you a detailed overview of the changes and improvements in each version. ## PHP variation Choose from several variations to suit your needs. Each variation is optimized for specific use cases. | Variation | Description | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `cli` | Minimal image for running PHP from the command line only. No web server included. | | `fpm` | Runs PHP-FPM without a web server. Ideal for microservices architectures where you need a dedicated PHP backend. | | `fpm-apache` | Combines PHP-FPM with Apache as a reverse proxy. Ideal for running WordPress with Docker. | | `fpm-nginx` | Combines PHP-FPM with NGINX as a reverse proxy. This is the traditional setup widely adopted for modern PHP applications, including Laravel. | | `frankenphp` | Modern application server built on Caddy that runs everything in a single process. Ideal for Laravel/Symfony applications with worker mode support, automatic HTTPS, and HTTP/2 and HTTP/3 protocols. | ### CLI The CLI variation is a minimal image designed for running PHP from the command line only. It does not include a web server. Use this variation when you need a disposable and repeatable method for easily running PHP scripts from your terminal. ::u-button --- ariaLabel: Learn more about the CLI variation class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about the CLI variation size: md to: https://serversideup.net/open-source/docker-php/docs/image-variations/cli trailing-icon: i-lucide-arrow-right variant: outline --- :: ### FPM The FPM (Fast Process Manager) variation runs PHP-FPM without a web server. This variation is ideal for microservices architectures where you need a dedicated PHP backend. Use this variation when you already have a separate proxy or load balancer handling static content and routing PHP requests to your FPM container. This is commonly used in larger-scale deployments. ::u-button --- ariaLabel: Learn more about the FPM variation class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about the FPM variation size: md to: https://serversideup.net/open-source/docker-php/docs/image-variations/fpm trailing-icon: i-lucide-arrow-right variant: outline --- :: ### FPM-Apache The FPM-Apache variation combines PHP-FPM with Apache as a reverse proxy. Apache serves static content directly and forwards PHP requests to PHP-FPM for processing. This is an excellent choice for WordPress sites and applications that specifically require Apache features or `.htaccess` support. ::u-button --- ariaLabel: Learn more about the FPM-Apache variation class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about the FPM-Apache variation size: md to: https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-apache trailing-icon: i-lucide-arrow-right variant: outline --- :: ### FPM-NGINX The FPM-NGINX variation combines PHP-FPM with NGINX as a reverse proxy. This is the traditional setup that has been widely adopted for modern PHP applications, including many Laravel deployments. NGINX serves static files efficiently and proxies PHP requests to PHP-FPM, providing excellent performance and flexibility. ::u-button --- ariaLabel: Learn more about the FPM-NGINX variation class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about the FPM-NGINX variation size: md to: https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-nginx trailing-icon: i-lucide-arrow-right variant: outline --- :: ### FrankenPHP The FrankenPHP variation is a modern application server built on top of the Caddy web server. It runs everything in a single process, eliminating the complexity of managing PHP-FPM and a separate web server. Use this variation for Laravel or Symfony applications that can benefit from worker mode, automatic HTTPS, and modern protocols like HTTP/2 and HTTP/3. This is the recommended variation for new projects seeking cutting-edge performance. ::u-button --- ariaLabel: Learn more about the FrankenPHP variation class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about the FrankenPHP variation size: md to: https://serversideup.net/open-source/docker-php/docs/image-variations/frankenphp trailing-icon: i-lucide-arrow-right variant: outline --- :: ### Unit (deprecated) ::warning --- target: _blank to: https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit --- In October 2025, NGINX stopped supporting NGINX Unit and archived the project. NGINX Unit has been removed from our project. [See the official announcement →](https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit){rel="nofollow"} :: ## Operating Systems Choosing an operating system comes down to a few preferences, but ultimately you need to make sure your dependencies are available for the operating system you choose. | Operating System | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | `debian` (default) | Debian is a popular Linux distribution that is known for its stability and reliability. It is the default operating system for our images. | | `alpine` | Alpine is a lightweight Linux distribution that is known for its small size and low resource usage. | ::warning{title="Choose an OS release that's still supported"} We continue to publish images on end-of-life operating system releases (like Debian Bullseye and Alpine 3.16) so legacy applications can be containerized as a first step. Be aware that EOL bases also ship EOL versions of other software like NGINX and OpenSSL — and no amount of `apt upgrade` will get you newer ones, because the distribution itself has stopped releasing fixes. Before picking a base, confirm it's currently supported: - [Debian release lifecycle](https://endoflife.date/debian){rel="nofollow"} - [Alpine Linux release lifecycle](https://endoflife.date/alpine-linux){rel="nofollow"} If you must start on an EOL base, treat it as a stepping stone. See [EOL versions and the legacy-modernization path](https://github.com/serversideup/docker-php/blob/main/SECURITY.md#eol-versions-and-the-legacy-modernization-path){rel="nofollow"} for the recommended migration approach. :: ### Specific versions ::note Not all operating systems are available for all image variations and PHP versions. Double check [Docker Hub](https://hub.docker.com/r/serversideup/php/tags){rel="nofollow"} and [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php){rel="nofollow"} for the most accurate list of available tags. :: We also support specific versions of operating systems. So this means that you can choose versions like: - `bookworm` - `trixie` - `alpine3.16` - `alpine3.17` - `alpine3.18` - `alpine3.19` - `alpine3.20` - `alpine3.21` - `alpine3.22` # Default Configurations ::lead-p `serversideup/php` is configured for real-world deployments right out of the box. This saves you many hours so you can launch faster than ever. :: ## Production-ready and optimized for Laravel & WordPress All values are defaulted to improve security and performance. We also spent the time to carefully review official documentation and include packages that are required specifically for Laravel and WordPress. ## Unprivileged by Default All images default to running as the OS-native `www-data` user instead of `root`. This is a critical security practice that limits what an attacker can do if they compromise your container. ### Why this matters Running as a non-root user means: - **Better security**: If someone exploits a vulnerability in your app, they can't gain root access to your container or host system - **Production-ready**: This follows Docker and Kubernetes security best practices - **Easier compliance**: Many security policies require non-root containers ### What this means for you Running unprivileged has two practical implications you should know about: #### Different ports for web servers On Linux systems, only root can bind to ports below 1024 (like 80 and 443). Since our containers don't run as root, they use higher port numbers by default: | **Variation** | **Default Ports** | | ------------- | ----------------------- | | cli | (none) | | fpm | 9000 | | fpm-nginx | HTTP: 8080, HTTPS: 8443 | | fpm-apache | HTTP: 8080, HTTPS: 8443 | | frankenphp | HTTP: 8080, HTTPS: 8443 | #### File permissions to be aware of The `www-data` user has different IDs depending on your base operating system: - **Debian**: UID/GID `33:33` - **Alpine**: UID/GID `82:82` ::tip We kept these as the OS defaults to maintain compatibility with other tools and images. If you're mounting volumes from your host machine or switching between Debian and Alpine, you might need to adjust file permissions. :: ### Running on standard HTTP/HTTPS ports (80 and 443) Don't worry—you have two easy options to run your app on the standard web ports: #### Docker port mapping (recommended for development) Use Docker's built-in port mapping to route traffic from port 80/443 on your host to the container's higher ports: ```bash [Terminal] docker run -p 80:8080 -p 443:8443 serversideup/php:8.5-fpm-nginx ``` This tells Docker: "Take traffic coming to port 80 on my machine and send it to port 8080 in the container." #### Reverse proxy (recommended for production) Use a reverse proxy like [Caddy](https://caddyserver.com/){rel="nofollow"}, [Traefik](https://traefik.io/){rel="nofollow"}, or [NGINX](https://nginx.org/){rel="nofollow"} to handle SSL certificates and route traffic to your containers. This is the preferred approach for production because it: - Automatically handles SSL certificate generation and renewal - Can route to multiple containers/services - Provides additional security features like rate limiting ::u-button --- ariaLabel: Learn about SSL configuration class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn about SSL configuration size: md to: https://serversideup.net/open-source/docker-php/docs/deployment-and-production/configuring-ssl trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Default Environment Variables Environment variables give you a ton of flexibility to customize your container without the complexity of mounting custom configuration files. By default, these images are set to production-ready values. You can read more about the available environment variables below. ::u-button --- ariaLabel: Learn more about environment variables class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about environment variables size: md to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Default PHP INI Settings | **PHP INI file** | **Description** | | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Image Default** :br:br `/usr/local/etc/php/conf.d/serversideup-docker-php.ini` | This is our production-ready PHP ini file that accepts environment variables. You can [review it in greater detail on GitHub](https://github.com/serversideup/docker-php/blob/main/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini){rel="nofollow"}. | | **Adding your own PHP INI file** :br:br `/usr/local/etc/php/conf.d/*.ini` | To set your own PHP settings, simply create a `.ini` file in the `/usr/local/etc/php/conf.d/` directory. It will be loaded automatically *after* our default ini file, so any settings you define will override the defaults. | ::u-button --- ariaLabel: Learn more about changing common PHP settings class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about changing common PHP settings size: md to: https://serversideup.net/open-source/docker-php/docs/customizing-the-image/changing-common-php-settings trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Default PHP Extensions The following extensions are installed by default: | **Extension** | **Description** | **Why we included it** | | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [opcache](https://www.php.net/manual/en/book.opcache.php){rel="nofollow"} | The Zend OPcache provides faster PHP execution through opcode caching and optimization. | This is a must-have for PHP performance. :br:br âš ī¸ OPcache is disabled by default but can easily be enabled with [`PHP_OPCACHE_ENABLE=1`](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification). | | [mysqli](https://www.php.net/manual/en/book.mysqli.php){rel="nofollow"} | The "MySQL Improved" extension is an older extension for connecting to MySQL 4.1 and above. | **Enabled for fpm-apache only**. This is a legacy MySQL connector required for WordPress. | | [pcntl](https://www.php.net/manual/en/intro.pcntl.php){rel="nofollow"} | Process Control support in PHP implements the Unix style of process creation, program execution, signal handling and process termination. | This is required for [Laravel queues and Laravel Horizon](https://laravel.com/docs/10.x/queues#timeout){rel="nofollow"} | | [pdo\_mysql](https://www.php.net/manual/en/ref.pdo-mysql.php){rel="nofollow"} | The MySQL PDO extension allows you to connect to MySQL databases. | MySQL and MariaDB databases are very popular. | | [pdo\_pgsql](https://www.php.net/manual/en/ref.pdo-pgsql.php){rel="nofollow"} | The PostgreSQL PDO extension allows you to connect to PostgreSQL databases. | PostgreSQL databases are very popular. | | [redis](https://www.php.net/manual/en/book.redis.php){rel="nofollow"} | The Redis extension allows you to connect to Redis databases. | Redis is very popular for caching and it's also required for [Laravel Horizon](https://laravel.com/docs/10.x/horizon){rel="nofollow"}. Our tests concluded adding this package only added 2MB to the image size. | | [zip](https://www.php.net/manual/en/book.zip.php){rel="nofollow"} | The Zip extension allows you to create and extract zip files. | We included this for the popularity of apps working with ZIP files. This package is also required if you're working with attachments on [Laravel Dusk](https://laravel.com/docs/10.x/dusk#attaching-files){rel="nofollow"}. | The official PHP images are already providing the following extensions: - ctype - curl - dom - fileinfo - filter - hash - mbstring - openssl - pcre - session - tokenizer - xml If you need to install additional extensions, you can use the guide below to learn more. ::u-button --- ariaLabel: Learn more about installing additional PHP extensions class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about installing additional PHP extensions size: md to: https://serversideup.net/open-source/docker-php/docs/customizing-the-image/installing-additional-php-extensions trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Default Operating System Packages The following packages are installed by default: | **Package** | **Description** | **Image variations** | **Why we included it** | | ------------------------------------------------ | ---------------------------------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `libfcgi-bin`:br (Debian) :br`fcgi`:br (Alpine) | FastCGI is a protocol for interfacing interactive programs with a web server. | \*-fpm :br \*-fpm-nginx :br \*-fpm-apache | This is required for the webserver to interface with PHP-FPM and the [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){rel="nofollow"} project. | | `gettext-base` (Debian) :br `gettext` (Alpine) | GNU gettext is a framework for translating user interfaces. | \*-fpm-nginx :br \*-fpm-apache | This is required for the `envsubst` command. We use this command to process templates on container initialization. | | `libstdc++6`:br (Debian) :br`libstdc++` (Alpine) | The GNU Standard C++ Library is a C++ standard library. | \*-frankenphp | This is [required for the watcher to run](https://github.com/php/frankenphp/blob/e917ab79742c9e4703023861fdc7a86cdb59da1e/Dockerfile#L135-L138){rel="nofollow"} with FrankenPHP. | | `procps` | The procps package contains programs for monitoring your system and its processes. | \* (only Debian images) | This is required for `pgrep` so we can use that for our native health checks. | | `shadow` | Shadow is required for the `usermod` command. | \*-alpine | This is required to change the UID and GID of the `www-data` user in `docker-php-serversideup-set-id`. | ::u-button --- ariaLabel: Learn which operating systems are available class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn which operating systems are available size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/choosing-an-image#operating-system trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Health Checks By default, all health checks for web servers (Apache, NGINX, FrankenPHP, etc.) are located at `/healthcheck`. You should receive an `OK` response if the container is healthy. For our `fpm` variation, we use the [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){rel="nofollow"} script to verify the health of PHP-FPM. This script is located at `/usr/local/bin/php-fpm-healthcheck`. The `cli` variation does not have a health check because it doesn't really make sense to have one. Would love to discuss more if you feel different. ::u-button --- ariaLabel: Learn more about health checks class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about health checks size: md to: https://serversideup.net/open-source/docker-php/docs/guide/using-healthchecks-with-laravel trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Default Entrypoint Scripts We created an entrypoint script that scans the `entrypoint.d` directory for other shell scripts to execute before the main container process starts. All scripts are executed in alphabetical order so you can have full control over what script execution order. We also provide a few default scripts to help you get started. | **Script Name** | **Description** | **Image variations** | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | `0-container-info.sh` | Shows basic execution information, such as Docker User, UID, GID, etc. | all | | `1-log-output-level.sh` | Sets PHP log output level to match `LOG_OUTPUT_LEVEL` | all | | `5-generate-ssl.sh` | Generates a self-signed SSL certificate and key for the container. | fpm-nginx, fpm-apache, frankenphp | | `5-fpm-pool-user.sh` | Sets the user and group of the PHP-FPM child processes to the values of `PHP_FPM_CHILD_PROCESS_USER` and `PHP_FPM_CHILD_PROCESS_GROUP` if the container is running as root. | fpm | | `10-init-webserver-config.sh` | Processes web server configuration templates, configures SSL (if enabled), and prepares web server for launch | \*-nginx :br \*-apache | | `50-laravel-automations.sh` | If `AUTORUN_ENABLED` is set to true, and a Laravel installation is detected, the following commands will automatically execute on container start: :br - `php artisan config:cache` :br - `php artisan route:cache` :br - `php artisan view:cache` :br - `php artisan event:cache` :br - `php artisan migrate --force` | all | If you want to add your own entrypoint scripts, use the guide below to learn more. ::u-button --- ariaLabel: Learn more about adding your own start up scripts class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about adding your own start up scripts size: md to: https://serversideup.net/open-source/docker-php/docs/customizing-the-image/adding-your-own-start-up-scripts trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Disabling Default Entrypoint Scripts We get it. Sometimes you just want our stuff to get out of the way. If you want full control to customize your image, all default entrypoint scripts can be disabled by setting `DISABLE_DEFAULT_CONFIG` to `true`. # Upgrade Guide ::lead-p This guide explains how `serversideup/php` releases work, how to choose an update strategy that fits your environment, and how to apply your own security patches when you need them. Use it to decide whether to ride our floating tags for automatic weekly updates, or pin to a version-pinned release and take responsibility for your own patching — both are valid choices, and the right one depends on how you balance reproducibility against staying current. For crossing a major version boundary (V3 → V4, V2 → V3), see the [Major Version Migrations](https://serversideup.net/open-source/docker-php/docs/guide/major-version-migrations) guide. :: ## Subscribe to repository updates Regardless if you are choosing to use floating tags or pinned releases, it is highly advised to subscribe to our releases. You can do this through the "Watch" button on our [GitHub](https://github.com/serversideup/docker-php){rel="nofollow"}. ![Watch Repository](https://serversideup.net/open-source/docker-php/images/docs/watch-repo.png){.max-w-md.w-full.h-auto.mx-auto} ::u-button --- ariaLabel: Subscribe to our Repo class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Subscribe to our Repo size: md target: _blank to: https://github.com/serversideup/docker-php trailing-icon: i-lucide-arrow-right variant: outline --- :: ## How our releases work All source code merges into the `main` branch. A pre-release is created for beta testing. After internal and community testing, we [publish a release on GitHub](https://github.com/serversideup/docker-php/releases){rel="nofollow"} with a detailed changelog. Publishing a release triggers a production build that pushes a new set of tags to Docker Hub and GitHub Packages. Production images are also rebuilt **automatically every Tuesday at 08:00 UTC** so floating tags pick up the latest upstream PHP and operating system security patches even when we haven't cut a release that week. ### Floating tags vs. version-pinned tags Every production build pushes two kinds of tags for each image in our matrix: ::note{title="Floating tags get updates automatically"} Tags like `8.4-fpm-nginx`, `8.4-fpm-nginx-bookworm`, and `latest` are **overwritten on every release and every weekly rebuild**. If you use a floating tag, you receive the latest security updates simply by running `docker pull`. :: ::caution{title="Version-pinned tags are immutable"} Tags that include a release suffix like `8.4-fpm-nginx-v4.3.5` are written **once** and never rebuilt. They stay exactly as they were the day they were published. If you pin to a version-pinned tag, you will **not** receive any future security updates from us — you are responsible for applying your own patches in your downstream image. :: ## Choosing your update strategy There are two valid ways to consume our images, and the right choice depends on how you balance reproducibility against staying current on security patches. | Strategy | Example tag | Update behavior | What you are responsible for | | ----------------------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Floating tag *(recommended for simple projects)* | `serversideup/php:8.4-fpm-nginx` | Auto-rebuilt weekly and on every release. `docker pull` brings in the latest patch. | Pulling regularly and testing before promoting to production. | | Version-pinned release *(recommended for high-volume production environments)* | `serversideup/php:8.4-fpm-nginx-v4.3.5` | Never rebuilt. Stays exactly as published. | All security updates — OS packages, Composer, and any other software inside the image. | For simple deployments, we recommend pinning to a **minor PHP version on a floating tag** like `8.4-fpm-nginx`. This keeps your PHP version stable for compatibility while still receiving weekly security updates from our rebuilds. Choose a **version-pinned release** when you need fully reproducible builds — for example, for supply-chain compliance or air-gapped environments — and have a process in place to apply your own updates. See [Applying your own security updates](https://serversideup.net/open-source/docker-php/#applying-your-own-security-updates) for how to do that. ## Choosing an image version If you do not select a specific patch version, you will receive automatic PHP patch updates through our floating-tag rebuilds. For example: - Major version (`8`) gives you the latest 8.x release. - Minor version (`8.4`) gives you the latest 8.4.x release. - Patch version (`8.4.1`) stays at exactly 8.4.1 — but on a floating tag, you still receive OS package updates on the weekly rebuild. - Version-pinned (`8.4.1-fpm-nginx-v4.3.5`) is fully frozen. If you use `latest`, you will always get the latest stable version of the CLI variation of PHP. ::u-button --- ariaLabel: Learn more about choosing an image class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about choosing an image size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/choosing-an-image trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Applying your own security updates There are two situations where you may need to apply updates yourself rather than waiting for us to publish a new image: 1. **You're pinned to a version-pinned tag** (e.g. `-v4.3.5`) for reproducibility, and a CVE has been disclosed since that release was cut. 2. **You're on a floating tag**, but a critical patch has landed upstream and you need it before our next weekly rebuild. In both cases, you apply updates by extending our image in your own Dockerfile. ::warning Any update you apply carries a risk of breaking the application inside the container. Always test before promoting changes to production. :: ### Update operating system packages Our images run as the unprivileged `www-data` user by default, so you need to switch to `root` before running package managers, then switch back. For Debian-based images: ```dockerfile [Dockerfile] # 8.4.1 is the PHP version, and 4.3.5 is our GitHub release version FROM serversideup/php:8.4.1-fpm-nginx-v4.3.5 USER root RUN apt-get update \ && apt-get upgrade -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* USER www-data ``` For Alpine-based images: ```dockerfile [Dockerfile] # 8.4.1 is the PHP version, and 4.3.5 is our GitHub release version FROM serversideup/php:8.4.1-fpm-nginx-alpine-v4.3.5 USER root RUN apk update \ && apk upgrade \ && rm -rf /var/cache/apk/* USER www-data ``` ### Update Composer Composer is installed in our images by copying the binary from the [official `composer` Docker image](https://hub.docker.com/_/composer){rel="nofollow"}. You can use the same pattern in your own Dockerfile to pull in a newer Composer release without waiting for us to publish a new image: ```dockerfile [Dockerfile] # 8.4.1 is the PHP version, and 4.3.5 is our GitHub release version FROM serversideup/php:8.4.1-cli-v4.3.5 # Pin Composer to a specific patch for reproducibility, # or use composer:2 for the latest 2.x release. COPY --from=composer:2.9.8 /usr/bin/composer /usr/bin/composer ``` This works for any of our variations (`cli`, `fpm`, `fpm-apache`, `fpm-nginx`, `frankenphp`). It's the same mechanism we use inside our images, so you're not fighting the base image — you're just upgrading the same file we put there. ::warning{title="NGINX, Apache, and other distro-provided software"} Web servers like NGINX and Apache are installed from your image's operating system package repositories. Running `apt-get upgrade` or `apk upgrade` only gets you the version your distribution currently ships. If you need a meaningfully newer NGINX — for example, to pick up a CVE fix that's only in a later distribution release — switch the base OS of our image (e.g. from `bullseye` to `bookworm`) or upgrade to a newer `serversideup/php` release. Avoid swapping in upstream `nginx.org` packages, as that can break our integration with PHP-FPM and S6 Overlay. :: ## Migrating between major versions Crossing a major version boundary — like V3 → V4 or V2 → V3 — is a separate concern from day-to-day upgrades. We track breaking changes, new features, and step-by-step checklists for each major release in a dedicated guide. ::u-button --- ariaLabel: Read the major version migration guide class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Read the major version migration guide size: md to: https://serversideup.net/open-source/docker-php/docs/guide/major-version-migrations trailing-icon: i-lucide-arrow-right variant: outline --- :: # Changelog ::lead-p All our changes are documented and published on our GitHub. [See our release notes on GitHub →](https://github.com/serversideup/docker-php/releases){rel="nofollow"} :: ### Choosing a version You may want to review [our guide on selecting the right image](https://serversideup.net/open-source/docker-php/docs/getting-started/choosing-an-image) to determine which version and image tag is best for you. ::u-button --- ariaLabel: See our release notes on GitHub class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: See our release notes on GitHub size: md target: _blank to: https://github.com/serversideup/docker-php/releases trailing-icon: i-lucide-arrow-right variant: outline --- :: # About ::lead-p We're taking the extra effort to open source as much as we can. Not only could this potentially help someone learn a little bit of Docker, but it makes it a *heck of a lot* easier for us to work with other people on our other open source projects. :: ::about :: ::tip{target="_blank" to="https://serversideup.net/products/"} If you appreciate our work, consider supporting us by buying our products or sponsoring us on GitHub. :: ## About Us We're [Dan](https://x.com/danpastori){rel="nofollow"} and [Jay](https://x.com/jaydrogers){rel="nofollow"} - a two person team with a passion for open source products. We created [Server Side Up](https://serversideup.net){rel="nofollow"} to help share what we learn. ### Find us at: - **📖 [Blog](https://serversideup.net){rel="nofollow"}** - Get the latest guides and free courses on all things web/mobile development. - **🙋 [Community](https://community.serversideup.net){rel="nofollow"}** - Get friendly help from our community members. - **đŸ¤ĩâ€â™‚ī¸ [Get Professional Help](https://serversideup.net/professional-support){rel="nofollow"}** - Get video + screen-sharing support from the core contributors. - **đŸ’ģ [GitHub](https://github.com/serversideup){rel="nofollow"}** - Check out our other open source projects. - **đŸ“Ģ [Newsletter](https://serversideup.net/subscribe){rel="nofollow"}** - Skip the algorithms and get quality content right to your inbox. - **đŸĨ [Twitter](https://x.com/serversideup){rel="nofollow"}** - You can also follow [Dan](https://x.com/danpastori){rel="nofollow"} and [Jay](https://x.com/jaydrogers){rel="nofollow"}. - **â¤ī¸ [Sponsor Us](https://github.com/sponsors/serversideup){rel="nofollow"}** - Please consider sponsoring us so we can create more helpful resources. ## Our products If you appreciate this project, be sure to check out our other projects. ### 📚 Books - **[The Ultimate Guide to Building APIs & SPAs](https://serversideup.net/ultimate-guide-to-building-apis-and-spas-with-laravel-and-nuxt3/){rel="nofollow"}**: Build web & mobile apps from the same codebase. - **[Building Multi-Platform Browser Extensions](https://serversideup.net/building-multi-platform-browser-extensions/){rel="nofollow"}**: Ship extensions to all browsers from the same codebase. ### đŸ› ī¸ Software-as-a-Service - **[Bugflow](https://bugflow.io/){rel="nofollow"}**: Get visual bug reports directly in GitHub, GitLab, and more. - **[SelfHost Pro](https://selfhostpro.com/){rel="nofollow"}**: Connect Stripe or Lemonsqueezy to a private docker registry for self-hosted apps. ### 🌍 Open Source - **[AmplitudeJS](https://serversideup.net/open-source/amplitudejs){rel="nofollow"}**: Open-source HTML5 & JavaScript Web Audio Library. - **[Spin](https://serversideup.net/open-source/spin/){rel="nofollow"}**: Laravel Sail alternative for running Docker from development → production. - **[Financial Freedom](https://github.com/serversideup/financial-freedom){rel="nofollow"}**: Open source alternative to Mint, YNAB, & Monarch Money. ## Special thanks This project wouldn't be possible without the help of some amazing people. We're grateful for their support and contributions. We wanted to give them a special shoutout here (in no particular order). ### [Chris Fidao](https://github.com/fideloper){rel="nofollow"} Majority of our knowledge came from Chris' course, [Shipping Docker](https://serversforhackers.com/shipping-docker){rel="nofollow"}. If you have yet to discover his content, you will be very satisfied with every course he has to offer. He's a great human being and excellent educator. Chris has continuously provided quality feedback that has deeply inspired the direction of this project. ### [Joel Clermont](https://github.com/joelclermont/){rel="nofollow"} Joel is one of the friendliest people that you'll ever meet. When he's not podcasting on his own show, [No Compromises](https://show.nocompromises.io/){rel="nofollow"}, he's busy helping other people in the PHP community. Joel's deep experience with PHP is rare to find, and we're grateful that we were able to pick his brain to bring you the best PHP Docker experience possible. ### [Patricio](https://github.com/ijpatricio){rel="nofollow"} If there's a new framework or tool in the web development world, Patricio probably has content for it on his [YouTube Channel](https://www.youtube.com/@PatricioOnCode){rel="nofollow"}. Patricio is a great resource for all things web development. Frontend, backend, DevOps -- it doesn't matter. Patricio graciously shared his wide spectrum of experiences with us and is a huge supporter of this project. ### [linuxserver.io](https://www.linuxserver.io/){rel="nofollow"} These guys are absolute aces when it comes to Docker development. They are a great resource for tons of open source Docker images. Check out their work, especially if you're a homelabber. # Contributing ::lead-p Thanks for your interest in contributing to this project! Please use read this entire guide before submitting a pull request. :: ## Improve the docs Improving the docs is very easy. If you find a simple mistake, look at the bottom of the Table Of Contents section and click "Edit this page". If you'd like to contribute bigger documentation changes, take a look at the `/docs` directory. Our entire site is available in that directory and you can see the process to easily install this on your machine by reading `/docs/README.md`. ::u-button --- ariaLabel: Learn how to contribute to the docs class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn how to contribute to the docs size: md target: _blank to: https://github.com/serversideup/docker-php/tree/main/docs trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Project dependencies You must have these installed on your system. - Docker (container engine): {rel="nofollow"} ## How things work 1. All files are stored in the `/src` folder 2. GitHub Actions will automatically build and deploy the images ## Running things locally To run a build, simply run `./scripts/dev.sh` (with Docker Desktop running). This will show you a help menu with all the available options. #### Example: Build a FPM-NGINX image running PHP 8.2.12 on Debian Bookworm ```bash [Terminal] bash scripts/dev.sh --variation fpm-nginx --version 8.2.12 --os bookworm ``` This will build `serversideup/php:8.2.12-fpm-nginx-bookworm` locally on your machine for testing and inspection. ### Published Beta Images We also have beta images that are published to our Docker Hub and GitHub Packages repositories. | âš™ī¸ Variation | 🚀 Version | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cli | **Debian Based** [![serversideup/php\:beta-8.5-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.5-cli?label=serversideup%2Fphp%3Abeta-8.5-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.5-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.4-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-cli?label=serversideup%2Fphp%3Abeta-8.4-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.3-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-cli?label=serversideup%2Fphp%3Abeta-8.3-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-cli?label=serversideup%2Fphp%3Abeta-8.2-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-cli?label=serversideup%2Fphp%3Abeta-8.1-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-cli?label=serversideup%2Fphp%3Abeta-8.0-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-cli\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-cli?label=serversideup%2Fphp%3Abeta-7.4-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-cli\&page=1\&ordering=-name){rel="nofollow"}:br**Alpine Based** [![serversideup/php\:beta-8.5-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.5-cli-alpine?label=serversideup%2Fphp%3Abeta-8.5-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.5-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-cli-alpine?label=serversideup%2Fphp%3Abeta-8.4-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.3-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-cli-alpine?label=serversideup%2Fphp%3Abeta-8.3-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.2-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-cli-alpine?label=serversideup%2Fphp%3Abeta-8.2-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.1-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-cli-alpine?label=serversideup%2Fphp%3Abeta-8.1-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.0-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-cli-alpine?label=serversideup%2Fphp%3Abeta-8.0-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-7.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-cli-alpine?label=serversideup%2Fphp%3Abeta-7.4-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-cli-alpine\&page=1\&ordering=-name){rel="nofollow"} | | fpm | **Debian Based** [![serversideup/php\:beta-8.5-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.5-fpm?label=serversideup%2Fphp%3Abeta-8.5-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.5-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm?label=serversideup%2Fphp%3Abeta-8.4-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.3-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm?label=serversideup%2Fphp%3Abeta-8.3-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm?label=serversideup%2Fphp%3Abeta-8.2-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm?label=serversideup%2Fphp%3Abeta-8.1-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm?label=serversideup%2Fphp%3Abeta-8.0-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm?label=serversideup%2Fphp%3Abeta-7.4-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm\&page=1\&ordering=-name){rel="nofollow"}:br**Alpine Based** [![serversideup/php\:beta-8.5-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.5-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.5-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.5-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.4-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.3-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.3-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.2-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.2-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.1-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.1-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.0-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.0-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-7.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm-alpine?label=serversideup%2Fphp%3Abeta-7.4-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm-alpine\&page=1\&ordering=-name){rel="nofollow"} | | fpm-apache | **Debian Based** [![serversideup/php\:beta-8.5-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.5-fpm-apache?label=serversideup%2Fphp%3Abeta-8.5-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.5-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm-apache?label=serversideup%2Fphp%3Abeta-8.4-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.3-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm-apache?label=serversideup%2Fphp%3Abeta-8.3-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm-apache?label=serversideup%2Fphp%3Abeta-8.2-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm-apache?label=serversideup%2Fphp%3Abeta-8.1-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm-apache?label=serversideup%2Fphp%3Abeta-8.0-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm-apache?label=serversideup%2Fphp%3Abeta-7.4-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm-apache\&page=1\&ordering=-name){rel="nofollow"} | | fpm-nginx | **Debian Based** [![serversideup/php\:beta-8.5-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.5-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.5-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.5-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.4-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.3-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.3-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.2-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.1-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.0-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm-nginx?label=serversideup%2Fphp%3Abeta-7.4-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm-nginx\&page=1\&ordering=-name){rel="nofollow"}:br**Alpine Based** [![serversideup/php\:beta-8.5-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.5-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.5-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.5-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.4-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.3-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.3-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.2-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.2-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.1-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.1-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.0-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.0-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-7.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-7.4-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm-nginx-alpine\&page=1\&ordering=-name){rel="nofollow"} | | frankenphp | **Debian Based** [![serversideup/php\:beta-8.5-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/beta-8.5-frankenphp?label=serversideup%2Fphp%3Abeta-8.5-frankenphp){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.5-frankenphp\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.4-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-frankenphp?label=serversideup%2Fphp%3Abeta-8.4-frankenphp){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-frankenphp\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.3-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-frankenphp?label=serversideup%2Fphp%3Abeta-8.3-frankenphp){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-frankenphp\&page=1\&ordering=-name){rel="nofollow"} :br**Alpine Based** [![serversideup/php\:beta-8.5-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.5-frankenphp-alpine?label=serversideup%2Fphp%3Abeta-8.5-frankenphp-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.5-frankenphp-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.4-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-frankenphp-alpine?label=serversideup%2Fphp%3Abeta-8.4-frankenphp-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-frankenphp-alpine\&page=1\&ordering=-name){rel="nofollow"} [![serversideup/php\:beta-8.3-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-frankenphp-alpine?label=serversideup%2Fphp%3Abeta-8.3-frankenphp-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-frankenphp-alpine\&page=1\&ordering=-name){rel="nofollow"} | #### Running a test web server: Sometimes you need to just run a test web server locally to see if your changes work. Below is a good example on how to quickly do this. ```bash [Terminal] docker run --rm -v $(pwd):/var/www/html -p 80:8080 -p 443:8443 serversideup/php:8.4-fpm-nginx ``` ## How PHP Versions are selected for distribution We use the official PHP versions as our base image. To identify which versions should be built, we use a file called `scripts/conf/php-versions-base-config.yml` to explicitly select what versions should be built and any special rules/settings for each version (like base OS, default versions, etc). We then use a `scripts/get-php-versions.sh` script to download the [latest active releases from PHP](https://www.php.net/releases/active.php){rel="nofollow"} and merge them into a final file called `scripts/conf/php-versions.yml`. The `php-versions.yml` file will include all final versions for tagging and building. We generate our tags with a file called `scripts/assemble-docker-tags.sh` which handles all the advanced logic of compiling our tags together. All the scripts above are designed to run locally and in GitHub Actions. Feel free to execute these scripts to see the help menus and how they work. ## GitHub Actions We use GitHub Actions exclusively to publish all of our releases. If the image exists from DockerHub or GitHub Packages, it will never be published from a local machine. See `.github/workflows/action_publish-beta-images.yml` for an example of how we publish our beta images. ## NGINX Versions We use the official NGINX repos to install the latest version of NGINX for each OS. The version to install is set by a build argument, which is loaded from the `scripts/conf/php-versions-base-config.yml` file. To view the current NGINX versions, run the following command: #### View NGINX versions ```bash [Terminal] ./scripts/get-nginx-versions.sh ``` This script will look at the official NGINX repos to find the latest version of NGINX for each OS. If you want to update the version, you can run the script with the `--write` flag. #### Update NGINX versions ```bash [Terminal] ./scripts/get-nginx-versions.sh --write ``` ### NGINX repository key verification - **Debian (APT)**: We import the official NGINX GPG key from `https://nginx.org/keys/nginx_signing.key` and verify it against a pinned fingerprint via the `SIGNING_FINGERPRINT` build arg. - **Alpine (APK)**: APK uses a raw RSA public key (`nginx_signing.rsa.pub`). We verify this key by pinning the SHA‑256 of the DER‑encoded public key via the `SIGNING_ALPINE_RSA_PUB_SHA256` build arg. You can provide multiple comma‑separated hashes to support key rotation. #### Compute the Alpine key hash when updating: ```bash [Terminal] curl -sS https://nginx.org/keys/nginx_signing.rsa.pub -o /tmp/nginx_signing.rsa.pub # macOS openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -outform DER 2>/dev/null | shasum -a 256 | awk '{print $1}' # Linux openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -outform DER 2>/dev/null | sha256sum | awk '{print $1}' ``` #### Build with the new hash (optionally include the old hash during rotation) ```bash [Terminal] docker build \ --build-arg SIGNING_ALPINE_RSA_PUB_SHA256="," \ -f src/variations/fpm-nginx/Dockerfile . ``` Reference: [Installing NGINX Open Source → Alpine packages](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#prebuilt_alpine){rel="nofollow"}. Why allow multiple hashes? This is optional, but useful during a short rotation window: - Ensure CI builds across branches/runners succeed while the upstream key change propagates. - Avoid flakes from CDN/caching delays where some environments still see the old key. - Let you pre-stage the new value before the official switch, then remove the old afterwards. If you control all builds centrally and can update quickly, pass a single hash. ## Helping out If you're really eager to help out, here are a few places to get started: - Help answer questions on [our GitHub Discussions](https://github.com/serversideup/docker-php/discussions){rel="nofollow"} and [our Discord](https://serversideup.net/discord){rel="nofollow"} - Chime in on [issues labeled "Help Wanted"](https://github.com/serversideup/docker-php/issues?q=is%3Aissue+is%3Aopen+label%3A%22%F0%9F%99%8F+Help+Wanted%22){rel="nofollow"} - [Open a feature request](https://github.com/serversideup/docker-php/discussions/66){rel="nofollow"} and tell us how we can improve - Run performance tests and share your results - Say good things and tag us on X (formerly Twitter): [@serversideup](https://x.com/serversideup){rel="nofollow"}, [@danpastori](https://x.com/danpastori){rel="nofollow"}, [@jaydrogers](https://x.com/jaydrogers){rel="nofollow"} # CLI ::lead-p The CLI variation is a minimal image designed for running PHP from the command line only. It does not include a web server. Use this variation for running commands like Composer, running one-off scripts, or executing PHP commands that don't require a web server. :: ## When to Use CLI Use the CLI variation when you need to: - Run Composer for dependency management - Execute one-off PHP scripts - Need a very small image size #### Perfect for - Running PHP locally without needing to install PHP on your host system. #### What's Inside | Item | Status | | ------------------------------- | --------------------------------- | | PHP CLI binary | ✅ | | Common PHP extensions | ✅ | | `composer` executable | ✅ | | `install-php-extensions` script | ✅ | | Essential system utilities | ✅ | | Native health checks | ❌ | | Web server | ❌ (no web server included) | | Process management | Single entrypoint, single process | | Exposed Ports | None | | Stop Signal | `SIGTERM` | ## Quick Start Here are a few quick examples to get you started. ### Docker CLI ```bash [Terminal] docker run -it -v $(pwd):/var/www/html serversideup/php:cli bash ``` The above command will mount your current directory as the `/var/www/html` directory in the container and open a bash shell inside the container where PHP is installed. To exit, just type `exit`. ### Docker Compose If you want something more repeatable, you can use Docker Compose to start a container with the CLI variation and mount your current directory as the `/var/www/html` directory in the container. ```yml [compose.yml] services: php: image: serversideup/php:cli volumes: - ./:/var/www/html ``` Once you have your `compose.yml` file set, you can use the `docker compose` cli to start a container with your configuration. ```bash [Terminal] docker compose run -it php bash ``` Or you can pass commands directly to the container without starting a shell. ::note Don't get confused. `php` is in this command twice because it's the name of the service and the command to run inside the container. If this is too confusing, you can set your service name to something else like `app` in your `compose.yml` file. :: ```bash [Terminal] docker compose run php php my-script.php ``` ### Further Customization If you need to customize the container further, reference the docs below: - [Environment Variable Specifications](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) - See which environment variables are available to customize common PHP settings. - [Command Reference](https://serversideup.net/open-source/docker-php/docs/reference/command-reference) - See which commands are available to run inside the container. # FPM ::lead-p The FPM variation runs PHP-FPM (FastCGI Process Manager) without a web server. It's designed to work alongside a separate web server or load balancer that handles static content and proxies PHP requests to this container. Use this variation when you're building microservices architectures or have a separate proxy layer handling HTTP traffic. :: ## When to Use FPM Use the FPM variation when you need to: - Separate your PHP processing from your web server layer - Build microservices where PHP runs as a dedicated backend service - Use a separate load balancer or API gateway to route traffic - Have an existing NGINX, Traefik, or other reverse proxy infrastructure - Scale your PHP processing independently from your web server #### Perfect for - Microservices architectures where separation of concerns is important - Kubernetes deployments with separate service containers - Large-scale deployments with dedicated load balancers - Advanced setups where you want full control over your proxy configuration #### What's Inside | Item | Status | | ------------------------------- | --------------------------------------------------------------------------------------------------------- | | PHP-FPM process manager | ✅ | | PHP CLI binary | ✅ | | Common PHP extensions | ✅ | | `composer` executable | ✅ | | `install-php-extensions` script | ✅ | | Essential system utilities | ✅ | | S6 Overlay (process supervisor) | ✅ | | Native health checks | ✅ (via [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){rel="nofollow"} script) | | Web server | ❌ (requires external web server) | | Process management | Single entrypoint, single process | | Exposed Ports | `9000` (FastCGI) | | Stop Signal | `SIGQUIT` | ## How FPM Works Unlike variations that include a web server, the FPM variation only runs PHP-FPM, which listens on port 9000 for FastCGI requests. You'll need a separate web server (like NGINX, Apache, or Caddy) to: ::steps{level="4"} #### Accept HTTP requests from clients #### Serve static files directly (CSS, JavaScript, images) #### Forward PHP requests to the FPM container on port 9000 #### Return the PHP-FPM response back to the client :: This architecture gives you maximum flexibility but requires more configuration than the all-in-one variations. ::note If you want a simpler setup with everything in one container, consider using the `fpm-nginx`, `fpm-apache`, or `frankenphp` variations instead. These include both the web server and PHP-FPM in a single container. :: ## Quick Start Here are a few examples to help you get started with the FPM variation. ### Docker Compose with Separate NGINX This example shows a common setup with PHP-FPM in one container and NGINX in another. ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm volumes: - ./:/var/www/html nginx: image: nginx:alpine ports: - "80:80" volumes: - ./:/var/www/html - ./nginx.conf:/etc/nginx/conf.d/default.conf depends_on: - php ``` And your NGINX configuration (`nginx.conf`): ```nginx [nginx.conf] server { listen 80; server_name localhost; root /var/www/html/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } ``` ::tip Notice how the `fastcgi_pass` directive points to `php:9000`. This is the service name from your Docker Compose file. Docker's networking allows services to communicate using their service names. :: ### Health Check The FPM variation includes [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){rel="nofollow"}, a POSIX-compliant script that monitors PHP-FPM's `/status` endpoint to verify the service is healthy. ::tip The `php-fpm-healthcheck` script can also monitor specific metrics like accepted connections or queue length. For example, you could fail the health check if the listen queue exceeds 10 processes: `php-fpm-healthcheck --listen-queue=10` :: ```yml [compose.yml] {7-10} services: php: image: serversideup/php:8.5-fpm volumes: - ./:/var/www/html healthcheck: test: ["CMD", "php-fpm-healthcheck", "--listen-queue=10"] interval: 10s timeout: 3s retries: 3 ``` ## Environment Variables The FPM variation supports extensive customization through environment variables. Here are some common ones: | Variable | Default | Description | | ------------------------------ | --------- | --------------------------------------------------------- | | `PHP_FPM_POOL_NAME` | `www` | Name of the PHP-FPM pool | | `PHP_FPM_PM_CONTROL` | `dynamic` | Process manager control (`dynamic`, `static`, `ondemand`) | | `PHP_FPM_PM_MAX_CHILDREN` | `20` | Maximum number of child processes | | `PHP_FPM_PM_START_SERVERS` | `2` | Number of child processes created on startup | | `PHP_FPM_PM_MIN_SPARE_SERVERS` | `1` | Minimum number of idle processes | | `PHP_FPM_PM_MAX_SPARE_SERVERS` | `3` | Maximum number of idle processes | | `PHP_MEMORY_LIMIT` | `256M` | Maximum memory a script can use | | `PHP_MAX_EXECUTION_TIME` | `99` | Maximum time a script can run (seconds) | ::tip --- to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification --- For a complete list of available environment variables, see the [Environment Variable Specification →](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification). :: ## Performance Tuning The FPM variation gives you fine-grained control over PHP process management. Here are some tuning tips: ### For High-Traffic Applications ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm environment: PHP_FPM_PM_CONTROL: "static" PHP_FPM_PM_MAX_CHILDREN: "50" PHP_MEMORY_LIMIT: "512M" ``` ### For Low-Memory Environments ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm environment: PHP_FPM_PM_CONTROL: "ondemand" PHP_FPM_PM_MAX_CHILDREN: "10" PHP_FPM_PM_PROCESS_IDLE_TIMEOUT: "10s" ``` ## Further Customization If you need to customize the container further, reference the docs below: - [Environment Variable Specification](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) - See which environment variables are available to customize PHP and PHP-FPM settings. - [Command Reference](https://serversideup.net/open-source/docker-php/docs/reference/command-reference) - See which commands are available to run inside the container. # FPM-Apache ::lead-p The FPM-Apache variation combines PHP-FPM with Apache as a reverse proxy in a single container. Apache serves static content directly and forwards PHP requests to PHP-FPM for processing. Use this variation when you need Apache-specific features, `.htaccess` support, or want an all-in-one solution for running PHP applications. :: ## When to Use FPM-Apache Use the FPM-Apache variation when you need to: - Run WordPress sites that rely on `.htaccess` configurations - Use Apache-specific modules like `mod_rewrite` or `mod_security` - Deploy applications that require `.htaccess` support - Want an all-in-one container with both web server and PHP processing - Need Apache's mature ecosystem and widespread documentation #### Perfect for - WordPress hosting with Docker - Legacy PHP applications that depend on Apache - Teams familiar with Apache configuration - Applications requiring `.htaccess` support #### What's Inside | Item | Status | | ------------------------------- | ---------------------------------------------- | | Apache web server | ✅ | | PHP-FPM process manager | ✅ | | PHP CLI binary | ✅ | | Common PHP extensions | ✅ | | `composer` executable | ✅ | | `install-php-extensions` script | ✅ | | Essential system utilities | ✅ | | S6 Overlay (process supervisor) | ✅ | | Native health checks | ✅ (via HTTP endpoint) | | `.htaccess` support | ✅ | | SSL/TLS support | ✅ (self-signed certificates or bring your own) | | Process management | S6 Overlay supervising both Apache and PHP-FPM | | Exposed Ports | `8080` (HTTP), `8443` (HTTPS) | | Stop Signal | `SIGQUIT` | ## How FPM-Apache Works This variation runs both Apache and PHP-FPM in a single container, managed by S6 Overlay (for the most accurate process supervision). Here's how requests flow: ::steps{level="4"} #### Client sends HTTP request The container listens on port 8080 (or 8443 for HTTPS) for incoming HTTP requests. #### Apache receives the request Apache receives the request and determines if it's a static file or PHP script. #### Check for static files Static files (CSS, JavaScript, images) are served directly by Apache. #### Forward PHP requests to PHP-FPM PHP requests are forwarded to PHP-FPM via FastCGI protocol. #### Process PHP requests with PHP-FPM PHP-FPM processes the PHP script and returns the result to Apache. #### Send the response back to the client Apache sends the response back to the client. :: S6 Overlay ensures both Apache and PHP-FPM are running and automatically restarts them if either process fails. ::note If you don't specifically need Apache, consider using the [`fpm-nginx`](https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-nginx) or [`frankenphp`](https://serversideup.net/open-source/docker-php/docs/image-variations/frankenphp) variations instead. They offer better performance for modern PHP applications. :: ## Quick Start Here are a few examples to help you get started with the FPM-Apache variation. ### Docker CLI ```bash [Terminal] docker run -p 80:8080 -v $(pwd):/var/www/html/public serversideup/php:8.5-fpm-apache ``` Your application will be available at `http://localhost`. The default document root is `/var/www/html/public`. ### Docker Compose ::warning Notice how we're mapping the current directory to `/var/www/html/`, but the actual default document root is `/var/www/html/public`. We're assuming you're creating the `public` directory and putting your PHP code in there. It's not best practice to expose your `compose.yml` file. See the [Installation guide](https://serversideup.net/open-source/docker-php/docs/getting-started/installation) for a full example. :: This is the recommended approach for local development and production deployments. ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-apache ports: - "80:8080" - "443:8443" volumes: - ./:/var/www/html environment: PHP_OPCACHE_ENABLE: "1" SSL_MODE: "full" ``` ::tip The FPM-Apache variation uses ports 8080 and 8443 (instead of 80 and 443) to allow the container to run as a non-root user for better security. :: ### WordPress Example The FPM-Apache variation is excellent for WordPress hosting: ```yml [compose.yml] services: wordpress: image: serversideup/php:8.5-fpm-apache ports: - "80:8080" - "443:8443" volumes: - ./wordpress:/var/www/html environment: SSL_MODE: "full" PHP_MEMORY_LIMIT: "512M" PHP_OPCACHE_ENABLE: "1" depends_on: - mariadb mariadb: image: mariadb:11 environment: MYSQL_ROOT_PASSWORD: rootpassword MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress volumes: - db_data:/var/lib/mysql volumes: db_data: ``` ### Health Check The FPM-Apache variation includes a built-in health check that verifies Apache is responding: ::note The health check endpoint is configurable via the `HEALTHCHECK_PATH` environment variable, which defaults to `/healthcheck`. :: If you are using Laravel, you can use the `/up` route to validate that Laravel is running and healthy. ::u-button --- ariaLabel: Learn more about using healthchecks with Laravel class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about using healthchecks with Laravel size: md to: https://serversideup.net/open-source/docker-php/docs/guide/using-healthchecks-with-laravel trailing-icon: i-lucide-arrow-right variant: outline --- :: ## SSL/TLS Support ::tip --- to: https://serversideup.net/open-source/docker-php/docs/deployment-and-production/configuring-ssl --- For more information on SSL, see the [Configuring SSL](https://serversideup.net/open-source/docker-php/docs/deployment-and-production/configuring-ssl) guide. :: The FPM-Apache variation includes built-in SSL support with self-signed certificates for development. ### Enabling SSL ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-apache ports: - "8080:8080" - "8443:8443" volumes: - ./:/var/www/html environment: SSL_MODE: "full" ``` Available SSL modes: - `off` - SSL disabled (default) - `mixed` - Both HTTP (8080) and HTTPS (8443) enabled - `full` - HTTPS only on port 8443 ### Custom SSL Certificates For production, use your own SSL certificates: ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-apache ports: - "443:8443" volumes: - ./:/var/www/html - ./certs/server.crt:/etc/ssl/private/self-signed-web.crt:ro - ./certs/server.key:/etc/ssl/private/self-signed-web.key:ro environment: SSL_MODE: "full" ``` ::warning For production deployments, consider using a reverse proxy like Traefik or Caddy to handle SSL termination instead of managing certificates in the container. :: ## Environment Variables The FPM-Apache variation supports extensive customization through environment variables. ### Apache Configuration | Variable | Default | Description | | ---------------------------------- | -------------------------------------- | ------------------------------------------------------ | | `APACHE_DOCUMENT_ROOT` | `/var/www/html/public` | Document root for Apache | | `APACHE_START_SERVERS` | `2` | Number of Apache server processes to start | | `APACHE_MIN_SPARE_THREADS` | `10` | Minimum idle threads | | `APACHE_MAX_SPARE_THREADS` | `75` | Maximum idle threads | | `APACHE_THREADS_PER_CHILD` | `25` | Number of threads per child process | | `APACHE_MAX_REQUEST_WORKERS` | `150` | Maximum simultaneous connections | | `APACHE_MAX_CONNECTIONS_PER_CHILD` | `0` | Requests before child process restarts (0 = unlimited) | | `SSL_MODE` | `off` | SSL mode: `off`, `mixed`, or `full` | | `SSL_CERTIFICATE_FILE` | `/etc/ssl/private/self-signed-web.crt` | Path to SSL certificate | | `SSL_PRIVATE_KEY_FILE` | `/etc/ssl/private/self-signed-web.key` | Path to SSL private key | | `HEALTHCHECK_PATH` | `/healthcheck` | Path for health check endpoint | ::tip --- to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification --- For a complete list of available environment variables, see the [Environment Variable Specification →](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification). :: ### PHP-FPM Configuration | Variable | Default | Description | | ------------------------------ | ---------- | --------------------------------------------------------- | | `PHP_FPM_POOL_NAME` | `www` | Name of the PHP-FPM pool | | `PHP_FPM_PM_CONTROL` | `ondemand` | Process manager control (`dynamic`, `static`, `ondemand`) | | `PHP_FPM_PM_MAX_CHILDREN` | `20` | Maximum number of child processes | | `PHP_FPM_PM_START_SERVERS` | `2` | Number of child processes created on startup | | `PHP_FPM_PM_MIN_SPARE_SERVERS` | `1` | Minimum number of idle processes | | `PHP_FPM_PM_MAX_SPARE_SERVERS` | `3` | Maximum number of idle processes | | `PHP_MEMORY_LIMIT` | `256M` | Maximum memory a script can use | | `PHP_MAX_EXECUTION_TIME` | `99` | Maximum time a script can run (seconds) | | `PHP_UPLOAD_MAX_FILE_SIZE` | `100M` | Maximum upload file size | | `PHP_POST_MAX_SIZE` | `100M` | Maximum POST request size | ::tip --- to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification --- For a complete list of available environment variables, see the [Environment Variable Specification →](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification). :: ## Further Customization If you need to customize the container further, reference the docs below: - [Environment Variable Specification](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) - See which environment variables are available to customize PHP and Apache settings. - [Command Reference](https://serversideup.net/open-source/docker-php/docs/reference/command-reference) - See which commands are available to run inside the container. # FPM-NGINX ::lead-p The FPM-NGINX variation combines PHP-FPM with NGINX as a reverse proxy in a single container. This is the traditional setup widely adopted for many PHP applications and is currently is the best balance of performance, stability, and compatibility. If you want the latest and greatest, consider using the [FrankenPHP variation →](https://serversideup.net/open-source/docker-php/docs/image-variations/frankenphp). :: ## When to Use FPM-NGINX Use the FPM-NGINX variation when you need to: - Run Laravel applications with excellent performance and stability - Want an all-in-one container with both web server and PHP processing - Need a fast, lightweight web server with low resource consumption - Serve static assets efficiently while processing PHP requests #### Perfect for - Laravel applications (this is our most popular variation for Laravel) - Modern PHP frameworks (Symfony, etc.) - API-first applications - Production deployments requiring high performance and stability #### What's Inside | Item | Status | | ------------------------------- | --------------------------------------------- | | NGINX web server | ✅ | | PHP-FPM process manager | ✅ | | PHP CLI binary | ✅ | | Common PHP extensions | ✅ | | `composer` executable | ✅ | | `install-php-extensions` script | ✅ | | Essential system utilities | ✅ | | S6 Overlay (process supervisor) | ✅ | | Native health checks | ✅ (via HTTP endpoint) | | SSL/TLS support | ✅ (self-signed certificates) | | Process management | S6 Overlay supervising both NGINX and PHP-FPM | | Exposed Ports | `8080` (HTTP), `8443` (HTTPS) | | Stop Signal | `SIGQUIT` | ## How FPM-NGINX Works This variation runs both NGINX and PHP-FPM in a single container, managed by S6 Overlay. Here's how requests flow: ::steps{level="4"} #### Client sends HTTP request The container listens on port 8080 (or 8443 for HTTPS) for incoming HTTP requests. #### NGINX receives the request NGINX receives the request and determines if it's a static file or PHP script. #### Check for static files Static files (CSS, JavaScript, images) are served directly by NGINX. #### Forward PHP requests to PHP-FPM PHP requests are forwarded to PHP-FPM via FastCGI protocol. #### Process PHP requests with PHP-FPM PHP-FPM processes the PHP script and returns the result to NGINX. #### Send the response back to the client NGINX sends the response back to the client. :: S6 Overlay ensures both NGINX and PHP-FPM are running and automatically restarts them if either process fails. ::tip This variation offers better performance than FPM-Apache for most modern PHP applications. NGINX is designed to handle high concurrency with lower resource consumption. :: ## Quick Start Here are a few examples to help you get started with the FPM-NGINX variation. ### Docker CLI ```bash [Terminal] docker run -p 80:8080 -v $(pwd):/var/www/html/public serversideup/php:8.5-fpm-nginx ``` Your application will be available at `http://localhost`. The default webroot is `/var/www/html/public`. ### Docker Compose ::warning Notice how we're mapping the current directory to `/var/www/html/`, but the actual default document root is `/var/www/html/public`. We're assuming you're creating the `public` directory and putting your PHP code in there. It's not best practice to expose your `compose.yml` file. See the [Installation guide](https://serversideup.net/open-source/docker-php/docs/getting-started/installation) for a full example. :: This is the recommended approach for local development and production deployments. ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-nginx ports: - "80:8080" volumes: - ./:/var/www/html environment: PHP_OPCACHE_ENABLE: "1" ``` ::tip The FPM-NGINX variation uses ports 8080 and 8443 (instead of 80 and 443) to allow the container to run as a non-root user for better security. :: ### Laravel Example The FPM-NGINX variation is perfectly suited for Laravel applications: ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-nginx ports: - "80:8080" - "443:8443" volumes: - .:/var/www/html environment: SSL_MODE: "full" PHP_OPCACHE_ENABLE: "1" depends_on: - mariadb - redis mariadb: image: mariadb:11 environment: MYSQL_ROOT_PASSWORD: rootpassword MYSQL_DATABASE: laravel MYSQL_USER: laravel MYSQL_PASSWORD: laravel volumes: - db_data:/var/lib/mysql redis: image: redis:alpine command: redis-server --appendonly yes volumes: - redis_data:/data volumes: db_data: redis_data: ``` ### Health Check The FPM-NGINX variation includes a built-in health check that verifies NGINX is responding: ::note The health check endpoint is configurable via the `HEALTHCHECK_PATH` environment variable, which defaults to `/healthcheck`. :: If you are using Laravel, you can use the `/up` route to validate that Laravel is running and healthy. ::u-button --- ariaLabel: Learn more about using healthchecks with Laravel class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about using healthchecks with Laravel size: md to: https://serversideup.net/open-source/docker-php/docs/guide/using-healthchecks-with-laravel trailing-icon: i-lucide-arrow-right variant: outline --- :: ## SSL/TLS Support The FPM-NGINX variation includes built-in SSL support with self-signed certificates for development. ### Enabling SSL ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-nginx ports: - "80:8080" - "443:8443" volumes: - ./:/var/www/html environment: SSL_MODE: "full" ``` Available SSL modes: - `off` - SSL disabled (default) - `mixed` - Both HTTP (8080) and HTTPS (8443) enabled - `full` - HTTPS only on port 8443 ### Custom SSL Certificates For production, use your own SSL certificates: ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-nginx ports: - "443:8443" volumes: - ./:/var/www/html - ./certs/server.crt:/etc/ssl/private/self-signed-web.crt:ro - ./certs/server.key:/etc/ssl/private/self-signed-web.key:ro environment: SSL_MODE: "full" ``` ::warning For production deployments, consider using a reverse proxy like Traefik or Caddy to handle SSL termination instead of managing certificates in the container. :: ## Environment Variables The FPM-NGINX variation supports extensive customization through environment variables. ### NGINX Configuration | Variable | Default | Description | | ---------------------------- | -------------------------------------- | ------------------------------------------------ | | `NGINX_WEBROOT` | `/var/www/html/public` | Document root for NGINX | | `NGINX_ACCESS_LOG` | `/dev/stdout` | Path to access log file | | `NGINX_ERROR_LOG` | `/dev/stderr` | Path to error log file | | `NGINX_CLIENT_MAX_BODY_SIZE` | `100M` | Maximum upload/request body size | | `NGINX_FASTCGI_BUFFERS` | `8 8k` | Number and size of FastCGI buffers | | `NGINX_FASTCGI_BUFFER_SIZE` | `8k` | Size of the first FastCGI response buffer | | `NGINX_SERVER_TOKENS` | `off` | Show NGINX version in headers (`on`/`off`) | | `NGINX_LISTEN_IP_PROTOCOL` | `all` | IP protocol to listen on (`all`, `ipv4`, `ipv6`) | | `SSL_MODE` | `off` | SSL mode: `off`, `mixed`, or `full` | | `SSL_CERTIFICATE_FILE` | `/etc/ssl/private/self-signed-web.crt` | Path to SSL certificate | | `SSL_PRIVATE_KEY_FILE` | `/etc/ssl/private/self-signed-web.key` | Path to SSL private key | | `HEALTHCHECK_PATH` | `/healthcheck` | Path for health check endpoint | ::tip --- to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification --- For a complete list of available environment variables, see the [Environment Variable Specification →](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification). :: ### PHP-FPM Configuration | Variable | Default | Description | | ------------------------------ | ---------- | --------------------------------------------------------- | | `PHP_FPM_POOL_NAME` | `www` | Name of the PHP-FPM pool | | `PHP_FPM_PM_CONTROL` | `ondemand` | Process manager control (`dynamic`, `static`, `ondemand`) | | `PHP_FPM_PM_MAX_CHILDREN` | `20` | Maximum number of child processes | | `PHP_FPM_PM_START_SERVERS` | `2` | Number of child processes created on startup | | `PHP_FPM_PM_MIN_SPARE_SERVERS` | `1` | Minimum number of idle processes | | `PHP_FPM_PM_MAX_SPARE_SERVERS` | `3` | Maximum number of idle processes | | `PHP_MEMORY_LIMIT` | `256M` | Maximum memory a script can use | | `PHP_MAX_EXECUTION_TIME` | `99` | Maximum time a script can run (seconds) | | `PHP_UPLOAD_MAX_FILE_SIZE` | `100M` | Maximum upload file size | | `PHP_POST_MAX_SIZE` | `100M` | Maximum POST request size | ## Performance Tuning Here are some tuning recommendations for different scenarios: ### For Production (low memory environments) ::note --- to: https://serversideup.net/open-source/docker-php/docs/deployment-and-production/packaging-your-app-for-deployment --- If you're running an application in production, you'll likely want to package your application inside an image for deployment. Click here to learn more. :: ```yml [compose.yml] services: php: # You'll likely replace this with your own custom image name image: serversideup/php:8.5-fpm-nginx environment: # Enable OPcache for production PHP_OPCACHE_ENABLE: "1" # NGINX Settings (adjust as needed) NGINX_FASTCGI_BUFFERS: "16 16k" NGINX_FASTCGI_BUFFER_SIZE: "32k" ``` ### For High-Traffic Applications ```yml [compose.yml] services: php: # You'll likely replace this with your own custom image name image: serversideup/php:8.5-fpm-nginx environment: # NGINX Settings NGINX_CLIENT_MAX_BODY_SIZE: "200M" NGINX_FASTCGI_BUFFERS: "32 32k" # PHP-FPM Settings (adjust as needed) PHP_FPM_PM_CONTROL: "static" PHP_FPM_PM_MAX_CHILDREN: "50" PHP_MEMORY_LIMIT: "512M" # OPcache Settings PHP_OPCACHE_ENABLE: "1" ``` ::note --- to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification --- These are just examples. Review the [Environment Variable Specification](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) for a complete list of available environment variables to match your needs. :: ## NGINX Configuration Unlike Apache's `.htaccess` files, NGINX uses configuration files. The FPM-NGINX variation comes pre-configured for Laravel and modern PHP applications. ### Default Configuration The default NGINX configuration includes: - FastCGI caching headers - Gzip compression - Security headers - Laravel-compatible URL rewriting - Static file optimization ### Custom NGINX Configuration You can add custom NGINX server configuration by mounting files: ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-nginx ports: - "80:8080" volumes: - ./:/var/www/html - ./custom-nginx.conf:/etc/nginx/conf.d/custom.conf:ro ``` Example custom configuration: ```nginx [custom-nginx.conf] # Add custom headers add_header X-Custom-Header "My Value" always; # Custom location block location /api { try_files $uri $uri/ /index.php?$query_string; # Additional settings for API endpoints client_max_body_size 50M; } # Rate limiting limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; location /api/ { limit_req zone=api burst=20 nodelay; } ``` ## Further Customization If you need to customize the container further, reference the docs below: - [Environment Variable Specification](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) - See which environment variables are available to customize PHP and NGINX settings. - [Command Reference](https://serversideup.net/open-source/docker-php/docs/reference/command-reference) - See which commands are available to run inside the container. # FrankenPHP ::lead-p The FrankenPHP variation is a modern application server built on top of the Caddy web server. It runs PHP and the web server in a single process, eliminating the complexity of managing PHP-FPM and a separate web server. This is the cutting-edge variation that offers worker mode, automatic HTTPS, and modern protocols like HTTP/2 and HTTP/3. It's the recommended variation for new Laravel projects seeking maximum performance. :: ## When to Use FrankenPHP Use the FrankenPHP variation when you need to: - Run Laravel Octane with maximum performance - Use worker mode to keep your application in memory - Get automatic HTTPS with zero configuration - Support modern protocols like HTTP/2 and HTTP/3 - Simplify your container architecture (single process) - Deploy Symfony applications with the Runtime component #### Perfect for - Laravel Octane applications - Symfony applications using the Runtime component - Modern PHP applications that can benefit from worker mode - Projects requiring automatic HTTPS - High-performance APIs that benefit from persistent connections - Teams wanting the latest and greatest in PHP application servers - Apps that need PHP 8.3 or newer ## Comparing FrankenPHP to Other Variations | Feature | FrankenPHP | FPM-NGINX | FPM-Apache | | ----------------- | -------------------------- | ------------- | ------------- | | Performance | âšĄī¸ Excellent (worker mode) | ✅ Very Good | ✅ Good | | Setup Complexity | ✅ Simple | ✅ Simple | ✅ Simple | | Worker Mode | ✅ Yes | ❌ No | ❌ No | | Automatic HTTPS | ✅ Yes | ❌ No | ❌ No | | HTTP/3 Support | ✅ Yes | ❌ No | ❌ No | | Laravel Octane | ✅ Native support | âš ī¸ Use Swoole | âš ī¸ Use Swoole | | .htaccess Support | ❌ No | ❌ No | ✅ Yes | | Maturity | âš ī¸ New | ✅ Mature | ✅ Mature | ::tip FrankenPHP is the newest variation and represents the future of PHP application servers. If you're starting a new project and can commit to modern practices, this is the variation to choose. :: #### Known Issues ::warning --- target: _blank to: https://frankenphp.dev/docs/known-issues/#standalone-binary-and-alpine-based-docker-images --- Some people are reporting performance issues on the `alpine` version of FrankenPHP. If you're experiencing this, consider using the `debian` version. :: FrankenPHP is cutting edge and is a very active project. Be sure to understand FrankenPHP's known issues before using it in production. If you're looking for better compatibility, consider using the [FPM-NGINX](https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-nginx) image. ::u-button --- ariaLabel: FrankenPHP known issues class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: View FrankenPHP's known issues size: md target: _blank to: https://frankenphp.dev/docs/known-issues/ trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Differences from Official FrankenPHP Images Our FrankenPHP images are built with production deployments and enterprise security in mind. While the official FrankenPHP images are great for getting started, we've made several enhancements that make these images more suitable for production environments, especially when deploying at scale with orchestrators like Kubernetes, Docker Swarm, or managed container platforms. ### Security-First Design: Unprivileged by Default Unlike the official FrankenPHP images that run as root, our images run as the unprivileged `www-data` user by default. This dramatically reduces your security footprint in production environments. **What this means for you:** - Containers run with minimal privileges, following security best practices - HTTP listens on port `8080` (instead of 80) - HTTPS listens on port `8443` (instead of 443) - Consistent with our other image variations for a unified experience ::tip This unprivileged design is consistent across all our image variations. Learn more about our [default configurations](https://serversideup.net/open-source/docker-php/docs/getting-started/default-configurations#unprivileged-by-default). :: ### Native Health Checks with Laravel Integration Health checks are critical for zero-downtime deployments, but the official images don't include them. Our images come with intelligent health check endpoints that work out of the box. **Built-in features:** - Default `/healthcheck` endpoint configured in Caddy - Configurable via `HEALTHCHECK_PATH` environment variable - Works with Laravel's native `/up` health check endpoint - Ensures your application is truly ready before accepting traffic ```yml [compose.yml] services: php: image: serversideup/php:8.5-frankenphp environment: # Use Laravel's built-in health check HEALTHCHECK_PATH: /up ``` ::tip Learn more about [using health checks with Laravel](https://serversideup.net/open-source/docker-php/docs/guide/using-healthchecks-with-laravel) to ensure your application is ready before accepting requests. :: ### Production-Grade Caddyfile Configuration The official FrankenPHP Dockerfile provides a basic Caddyfile to get started. We've spent considerable time crafting a production-ready configuration that includes security hardening, performance optimizations, and enterprise features. **What's included:** - **CloudFlare integration** - Trusted IP addresses configured automatically - **Security headers** - Best-practice headers included by default - **Performance rules** - Smart caching and compression configured - **Flexible logging** - Configurable output formats and levels - **Self-signed certificates** - Automatic generation for development environments - **Let's Encrypt support** - Easy configuration for automatic SSL certificates ### Designed for Orchestrator Deployments FrankenPHP's tight integration with Caddy enables amazing features like automatic Let's Encrypt SSL certificates. However, in most production deployments, you're likely using a load balancer or reverse proxy for SSL termination, making Caddy's automatic SSL less useful and potentially problematic at scale. **Our orchestrator-first approach:** - Assumes SSL/TLS termination happens at the load balancer level - Optimized for zero-downtime rolling deployments - Works seamlessly with Kubernetes, Docker Swarm, and managed platforms - Simplifies scaling from one container to hundreds ![Reverse Proxy](https://serversideup.net/open-source/docker-php/images/docs/reverse-proxy-ssl-zerodowntime.png){:zoom='false'} ::note You can still use Caddy's automatic HTTPS with Let's Encrypt if you prefer. See our [Configuring SSL](https://serversideup.net/open-source/docker-php/docs/deployment-and-production/configuring-ssl) guide for all available options. :: ### Consistent Environment Variable Experience Just like all our other PHP variations, the FrankenPHP images support the same environment variables and helper scripts you're already familiar with. **Unified configuration across all variations:** - `SSL_MODE` - Control SSL behavior (`off`, `mixed`, `full`) - `LOG_OUTPUT_LEVEL` - Adjust logging verbosity - PHP INI settings via environment variables - Helper scripts for permissions management - Consistent startup script behavior This means you can switch between variations (FrankenPHP, FPM-NGINX, FPM-Apache) with minimal configuration changes. ::u-button --- ariaLabel: View environment variable specification class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: View all environment variables size: md to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification trailing-icon: i-lucide-arrow-right variant: outline --- :: ### More Operating System Variations We compile FrankenPHP from source, which allows us to support multiple operating systems for maximum flexibility. **Available platforms:** - Debian Bookworm (12) - Debian Trixie (13) - Alpine 3.21 - Alpine 3.22 This gives you the freedom to choose the base OS that best fits your infrastructure and security requirements. #### What's Inside | Item | Status | | ------------------------------- | ------------------------------------------------------------ | | FrankenPHP application server | ✅ | | Caddy web server | ✅ (built-in) | | PHP CLI binary | ✅ | | Common PHP extensions | ✅ | | `composer` executable | ✅ | | `install-php-extensions` script | ✅ | | Essential system utilities | ✅ | | Worker mode support | ✅ | | Automatic HTTPS | ✅ | | HTTP/2 support | ✅ | | HTTP/3 support | ✅ | | Mercure (real-time) | ✅ | | Native health checks | ✅ (via HTTP endpoint) | | SSL/TLS support | ✅ (automatic + self-signed) | | Process management | Single process (no supervisor needed) | | Exposed Ports | `8080` (HTTP), `8443` (HTTPS + HTTP/3), `2019` (Caddy admin) | | Stop Signal | `SIGTERM` | ## Classic Mode vs Worker Mode Unlike traditional setups that require a separate web server and PHP-FPM, FrankenPHP runs everything in a single process. It also operates in two modes: #### Classic Mode (Default) - FrankenPHP functions like a traditional PHP server (similar to PHP-FPM) - Each request bootstraps your application fresh - No additional configuration needed - Safe for any existing PHP applications #### Worker Mode (Advanced) Worker mode is FrankenPHP's killer feature. Instead of bootstrapping your application for every request, it stays loaded in memory: - **Traditional**: Bootstrap app → Handle request → Teardown → Repeat - **Worker Mode**: Bootstrap app once → Handle requests indefinitely This can result in dramatic performance improvements for Laravel applications. ::tip Worker mode is perfect for Laravel Octane. Your application boots once and handles thousands of requests without reloading, dramatically improving response times. :: ## How FrankenPHP Works ::steps{level="4"} #### Client sends request The client sends an HTTP request to port 8080 (or 8443 for HTTPS). #### FrankenPHP receives and processes the request FrankenPHP receives and processes the request directly in a single process. This includes: 1. Static files 2. PHP requests #### Send response back to client The response is sent back to the client. :: ## Quick Start Here are a few examples to help you get started with the FrankenPHP variation. ### Docker CLI ```bash [Terminal] docker run -p 80:8080 -v $(pwd):/var/www/html/public serversideup/php:8.5-frankenphp ``` Your application will be available at `http://localhost`. The default webroot is `/var/www/html/public`. ### Docker Compose Here's a basic example getting FrankenPHP up and running with Docker Compose. ::warning Don't forget to create a `public` directory and put your PHP code in there. :: ::code-tree{default-value="compose.yml"} ```yml [compose.yml] services: php: # Choose our PHP version and variation image: serversideup/php:8.5-frankenphp # Expose and map HTTP and HTTPS ports ports: - 80:8080 - 443:8443 # Mount current directory to /var/www/html volumes: - ./:/var/www/html # Support both HTTP and HTTPS environment: SSL_MODE: mixed ``` ```php [public/index.php] ``` :: ::tip The FrankenPHP variation uses ports 8080 and 8443 (instead of 80 and 443) to allow the container to run as a non-root user for better security. :: ### Laravel Octane Laravel Octane natively supports FrankenPHP. Use our guide below to learn more. ::u-button --- ariaLabel: Learn more about Laravel Octane class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about Laravel Octane size: md to: https://serversideup.net/open-source/docker-php/docs/framework-guides/laravel/octane trailing-icon: i-lucide-arrow-right variant: outline --- :: ### Health Check The FrankenPHP variation includes a built-in health check that verifies the server is responding: ::note The health check endpoint is configurable via the `HEALTHCHECK_PATH` environment variable, which defaults to `/healthcheck`. :: If you are using Laravel, you can use the `/up` route to validate that Laravel is running and healthy. ::u-button --- ariaLabel: Learn more about using healthchecks with Laravel class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about using healthchecks with Laravel size: md to: https://serversideup.net/open-source/docker-php/docs/guide/using-healthchecks-with-laravel trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Automatic HTTPS One of FrankenPHP's standout features is automatic HTTPS powered by Caddy. It can automatically obtain and renew SSL certificates from Let's Encrypt. ::tip --- to: https://serversideup.net/open-source/docker-php/docs/deployment-and-production/configuring-ssl --- See our [Configuring SSL](https://serversideup.net/open-source/docker-php/docs/deployment-and-production/configuring-ssl) guide for more information on the best strategies for running SSL in production. :: ### Enabling Automatic HTTPS ```yml [compose.yml] services: php: image: serversideup/php:8.5-frankenphp ports: - "80:8080" - "443:8443" volumes: - ./:/var/www/html environment: CADDY_AUTO_HTTPS: "on" # Your domain for automatic certificate SERVER_NAME: "example.com" SSL_MODE: "full" ``` ::warning Automatic HTTPS requires a public domain name and ports 80/443 accessible from the internet for Let's Encrypt validation. For local development, use self-signed certificates with `SSL_MODE`. :: ### SSL Modes for Development For local development, use the `SSL_MODE` environment variable: ```yml [compose.yml] services: php: image: serversideup/php:8.5-frankenphp ports: - "80:8080" - "443:8443" volumes: - ./:/var/www/html environment: SSL_MODE: "full" ``` Available SSL modes: - `off` - SSL disabled (default) - `mixed` - Both HTTP (8080) and HTTPS (8443) enabled - `full` - HTTPS only on port 8443 Learn more about SSL modes in the [Configuring SSL](https://serversideup.net/open-source/docker-php/docs/deployment-and-production/configuring-ssl) guide. ::u-button --- ariaLabel: Learn more about SSL modes class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about SSL modes size: md to: https://serversideup.net/open-source/docker-php/docs/deployment-and-production/configuring-ssl trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Environment Variables The FrankenPHP variation supports extensive customization through environment variables. ### FrankenPHP/Caddy Configuration | Variable | Default | Description | | ------------------------------- | -------------------------------------- | ----------------------------------------------------- | | `FRANKENPHP_CONFIG` | `""` | FrankenPHP-specific configuration (e.g., worker mode) | | `CADDY_SERVER_ROOT` | `/var/www/html/public` | Document root for the application | | `CADDY_AUTO_HTTPS` | `off` | Enable automatic HTTPS (`on`/`off`) | | `CADDY_HTTP_PORT` | `8080` | HTTP port | | `CADDY_HTTPS_PORT` | `8443` | HTTPS port | | `CADDY_ADMIN` | `off` | Caddy admin API endpoint | | `CADDY_LOG_FORMAT` | `console` | Log format (`console`/`json`) | | `CADDY_LOG_OUTPUT` | `stdout` | Log output destination | | `CADDY_GLOBAL_OPTIONS` | `""` | Additional Caddy global options | | `CADDY_SERVER_EXTRA_DIRECTIVES` | `""` | Additional Caddy server directives | | `SSL_MODE` | `off` | SSL mode: `off`, `mixed`, or `full` | | `SSL_CERTIFICATE_FILE` | `/etc/ssl/private/self-signed-web.crt` | Path to SSL certificate | | `SSL_PRIVATE_KEY_FILE` | `/etc/ssl/private/self-signed-web.key` | Path to SSL private key | | `HEALTHCHECK_PATH` | `/healthcheck` | Path for health check endpoint | | `SERVER_NAME` | `""` | Domain name for automatic HTTPS | ::tip --- to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification --- For a complete list of available environment variables, see the [Environment Variable Specification →](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification). :: ### PHP Configuration | Variable | Default | Description | | --------------------------------- | ------- | --------------------------------------------- | | `PHP_MEMORY_LIMIT` | `256M` | Maximum memory a script can use | | `PHP_MAX_EXECUTION_TIME` | `99` | Maximum time a script can run (seconds) | | `PHP_UPLOAD_MAX_FILE_SIZE` | `100M` | Maximum upload file size | | `PHP_POST_MAX_SIZE` | `100M` | Maximum POST request size | | `PHP_OPCACHE_ENABLE` | `0` | Enable OPcache (`0`/`1`) | | `PHP_OPCACHE_REVALIDATE_FREQ` | `2` | How often to check for file changes (seconds) | | `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `1` | Whether to validate timestamps (`0`/`1`) | ## Caddy Configuration FrankenPHP uses Caddy's configuration format (Caddyfile) instead of NGINX configuration. ### Adding Custom Options There are a few areas where you can use environment variables to customize your Caddy configuration: | Variable | Description | Official Documentation | | ------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `CADDY_GLOBAL_OPTIONS` | Global Caddy options | [Caddy Global Options](https://caddyserver.com/docs/caddyfile/options){rel="nofollow"} | | `CADDY_SERVER_EXTRA_DIRECTIVES` | Server-specific Caddy directives | [Caddy Server Directives](https://caddyserver.com/docs/caddyfile/directives){rel="nofollow"} | | `CADDY_PHP_SERVER_OPTIONS` | PHP-specific Caddy directives (site-specific) | [FrankenPHP PHP Server Options](https://frankenphp.dev/docs/config/#caddyfile-config){rel="nofollow"} | | `FRANKENPHP_CONFIG` | FrankenPHP-specific configuration (global) | [FrankenPHP Configuration](https://frankenphp.dev/docs/config/#caddyfile-config){rel="nofollow"} | ```yml [compose.yml] services: php: image: serversideup/php:8.5-frankenphp environment: CADDY_SERVER_EXTRA_DIRECTIVES: | # Add custom headers header { X-Custom-Header "My Value" -Server } ``` ## Further Customization If you need to customize the container further, reference the docs below: - [Environment Variable Specification](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) - See which environment variables are available to customize PHP and Caddy settings. - [Command Reference](https://serversideup.net/open-source/docker-php/docs/reference/command-reference) - See which commands are available to run inside the container. - [FrankenPHP Documentation](https://frankenphp.dev/){rel="nofollow"} - Official FrankenPHP documentation for advanced features. - [Caddy Documentation](https://caddyserver.com/docs/){rel="nofollow"} - Official Caddy documentation for web server configuration. # Unit (Deprecated) ## NGINX Unit Has Been Archived In October 2025, NGINX officially archived the NGINX Unit project and stopped all maintenance. If you're seeing this page, you're likely using our Unit-based images and wondering what to do next. ::caution --- target: _blank to: https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit --- **The Unit variation is deprecated and has been removed from our project.** [View official NGINX announcement →](https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit){rel="nofollow"} :: **The good news:** Your application will continue to work, and you have time to plan your migration. Below, we'll answer your most important questions and guide you through your options. ## Common Questions ### Will my application stop working immediately? No. Your existing containers will continue to run without interruption. However: - **No security updates:** NGINX Unit will not receive security patches - **No bug fixes:** Any issues with Unit itself won't be resolved - **No new PHP versions:** Unit may not support future PHP releases You should prioritize your migration, but you're not in a downtime situation. ### What should I migrate to? We recommend **FrankenPHP** as the best alternative because it offers: - **Single-process architecture** (similar to Unit's design) - **Built-in HTTP/2 and HTTP/3 support** - **Active development** by the PHP community - **Laravel Octane support** for enhanced performance - **Better performance** than traditional PHP-FPM setups ::u-button --- ariaLabel: Learn about FrankenPHP variation class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn about FrankenPHP size: md to: https://serversideup.net/open-source/docker-php/docs/image-variations/frankenphp trailing-icon: i-lucide-arrow-right variant: outline --- :: ### What if FrankenPHP doesn't work for me? You have other proven options: - **[FPM + NGINX](https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-nginx)** - Traditional, highly scalable setup (recommended for most production apps) - **[FPM + Apache](https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-apache)** - If you need `.htaccess` support or prefer Apache - **[CLI](https://serversideup.net/open-source/docker-php/docs/image-variations/cli)** - For queue workers, scheduled tasks, and CLI-only workloads All of these variations are actively maintained and production-ready. ### How urgent is this migration? **Timeline:** - **Now:** You can still pull `serversideup/php:unit` images, but they will not receive any future feature or security updates. **Recommendation:** Start planning your migration now. Don't rush, but don't delay indefinitely. ### Where can I get help? We're here to support you through this transition: - **Community Support:** [Post on our forum](https://serversideup.net/php/community){rel="nofollow"} or [join our Discord](https://serversideup.net/discord){rel="nofollow"} for migration questions - **Migration Assistance:** Ask questions specific to your setup - **Documentation:** Follow our comprehensive guides for each variation ## Need More Information? Explore the documentation for your chosen variation: - [FrankenPHP Documentation](https://serversideup.net/open-source/docker-php/docs/image-variations/frankenphp) - [FPM + NGINX Documentation](https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-nginx) - [FPM + Apache Documentation](https://serversideup.net/open-source/docker-php/docs/image-variations/fpm-apache) Each variation includes detailed configuration examples, performance tuning tips, and deployment guides. # Automations ::lead-p `serversideup/php` has a "Laravel Automations" script that helps automate common tasks to maintain your Laravel application and improve it's performance. By default, the script is **DISABLED**. We only recommend enabling this script in production environments. :: ## What the script does ::note In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once the main part of the script is enabled, you can control the individual tasks by setting the corresponding environment variables to `true` or `false`. See our [variable reference document](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) for more details. :: | Environment Variable | Default | Description | | ----------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AUTORUN_ENABLED` | `false` | Enables the Laravel Automations script. :br **â„šī¸ Note:** This must be set to `true` for the script to run. | | `AUTORUN_DEBUG` | `false` | Enables a special debug mode, specifically for the Laravel Automations script. | | `AUTORUN_LARAVEL_CONFIG_CACHE` | `true` | `php artisan config:cache`: Caches the configuration files into a single file. | | `AUTORUN_LARAVEL_EVENT_CACHE` | `true` | `php artisan event:cache`: Creates a manifest of all your application's events and listeners. | | `AUTORUN_LARAVEL_MIGRATION` | `true` | `php artisan migrate`: Runs migrations. | | `AUTORUN_LARAVEL_MIGRATION_DATABASE` | `null` | Run migrations on a specific database. In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). If `null`, it will use the default database connection. | | `AUTORUN_LARAVEL_MIGRATION_FORCE` | `true` | Force migrations to run in production without confirmation. Set to `false` to disable the `--force` flag. | | `AUTORUN_LARAVEL_MIGRATION_ISOLATION` | `false` | Run your migrations with the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations){rel="nofollow"} flag. :br **â„šī¸ Note:** Requires Laravel v9.38.0+. Only works with `default` migration mode. | | `AUTORUN_LARAVEL_MIGRATION_MODE` | `default` | Migration mode: `default`, `fresh`, or `refresh`. :br **âš ī¸ Warning:** `fresh` and `refresh` drop all tables. | | `AUTORUN_LARAVEL_MIGRATION_SEED` | `false` | Automatically seed the database after migrations using the `--seed` flag. | | `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK` | `false` | Skip the database connection check before running migrations. | | `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` | `30` | Number of seconds to wait for database connection before timing out during migrations. | | `AUTORUN_LARAVEL_OPTIMIZE` | `true` | `php artisan optimize`: Optimizes the application. | | `AUTORUN_LARAVEL_ROUTE_CACHE` | `true` | `php artisan route:cache`: Caches the routes. | | `AUTORUN_LARAVEL_STORAGE_LINK` | `true` | `php artisan storage:link`: Creates a symbolic link from `public/storage` to `storage/app/public`. | | `AUTORUN_LARAVEL_VIEW_CACHE` | `true` | `php artisan view:cache`: Caches the views. | ## Database Connection Checks Before running migrations, the automation script performs connection checks to ensure your database is ready. Understanding this process helps you configure timeouts and troubleshoot connection issues. ::note You can skip database connection checks entirely by setting `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK=true`. This is useful when you're certain your database is ready or when using alternative connection verification methods. :: ::steps{level="3"} ### Clear configuration cache The script runs `php artisan config:clear` to ensure fresh database configuration is loaded before attempting any connections. ### Attempt database connection The script tests the database connection using a retry mechanism: - Attempts to connect every second - Continues for up to `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` seconds (default: 30) - Shows connection progress in real-time - Logs detailed attempts every 5 seconds when `AUTORUN_DEBUG=true` ### Verify each database connection If you've specified multiple databases via `AUTORUN_LARAVEL_MIGRATION_DATABASE`, the script waits for each database connection individually before proceeding with migrations for that database. ### Run migrations Once the database connection is confirmed, the script executes the appropriate migration command based on your configuration. :: ::tip Set `AUTORUN_DEBUG=true` to see detailed connection attempt logs, which is helpful for troubleshooting connection issues. :: ## Laravel Artisan Commands Below is an overview of what Laravel Artisan commands do and how they can be configured. ### php artisan storage\:link Creates a symbolic link from `public/storage` to `storage/app/public`. ::u-button --- ariaLabel: Read more about storage links class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Read more about storage links size: md target: _blank to: https://laravel.com/docs/12.x/filesystem#the-public-disk trailing-icon: i-lucide-arrow-right variant: outline --- :: ### php artisan migrate Before running migrations, we ensure the database is online and ready to accept connections. By default, we will wait 30 seconds before timing out. #### Migration Modes You can control how migrations run using `AUTORUN_LARAVEL_MIGRATION_MODE`: | Mode | Description | | --------- | -------------------------------------------------------------------------- | | `default` | Runs `php artisan migrate` - standard forward migrations | | `fresh` | Runs `php artisan migrate:fresh` - drops all tables and re-runs migrations | | `refresh` | Runs `php artisan migrate:refresh` - rolls back and re-runs migrations | ::caution Using `fresh` or `refresh` modes will **drop all tables** in your database. Only use these in development or testing environments. :: #### Force Flag By default, migrations run with the `--force` flag to bypass production warnings. You can disable this by setting `AUTORUN_LARAVEL_MIGRATION_FORCE` to `false`. #### Seeding You can automatically seed your database after migrations by setting `AUTORUN_LARAVEL_MIGRATION_SEED` to `true`. This adds the `--seed` flag to your migration command. #### Specific Database Migrations If you need to specify the exact database connection to use for migrations, you can set `AUTORUN_LARAVEL_MIGRATION_DATABASE` to the name of the database connection you want to use. | Use case | Description | Value | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | | Single database | Run migrations on the `mysql` database connection. | `AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql` | | Multiple databases | In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). | `AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql,pgsql` | #### Isolated Migrations You can enable the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations){rel="nofollow"} flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. **Special Notes for Isolated Migrations:** - Requires Laravel v9.38.0+ - Only works with `default` migration mode (not compatible with `fresh` or `refresh`) - Your application must be using the memcached, redis, dynamodb, database, file, or array cache driver as your application's default cache driver. In addition, all servers must be communicating with the same central cache server. ::u-button --- ariaLabel: Read more about migrations class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Read more about migrations size: md target: _blank to: https://laravel.com/docs/12.x/migrations#running-migrations trailing-icon: i-lucide-arrow-right variant: outline --- :: ### php artisan optimize Laravel comes with an artisan command called `optimize`, which will optimize the application by caching the configuration, routes, views, and events all in one command. You can disable any cache features by setting the corresponding environment variable to `false` (for example, `AUTORUN_LARAVEL_CONFIG_CACHE` would disable configuration caching). If your application is running Laravel v11.38.0 or higher, we will utilize the `optimize --except` parameter to exclude any cache features you have disabled. Otherwise, we will run the individual optimizations separately. It's possible to disable the `optimize` command by setting `AUTORUN_LARAVEL_OPTIMIZE` to `false`, but the major advantage of using the `optimize` command is other dependencies may hook into this action and run other commands. ::u-button --- ariaLabel: Read more about optimizing Laravel class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Read more about optimizing Laravel size: md target: _blank to: https://laravel.com/docs/12.x/deployment#optimization trailing-icon: i-lucide-arrow-right variant: outline --- :: ### php artisan config\:cache This command caches all configuration files into a single file, which can then be quickly loaded by Laravel. Once the configuration is cache, the `.env` file will no longer be loaded. ::u-button --- ariaLabel: Read more about configuration caching class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Read more about configuration caching size: md target: _blank to: https://laravel.com/docs/12.x/configuration#configuration-caching trailing-icon: i-lucide-arrow-right variant: outline --- :: ### php artisan route\:cache This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. ::u-button --- ariaLabel: Read more about route caching class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Read more about route caching size: md target: _blank to: https://laravel.com/docs/12.x/routing#route-caching trailing-icon: i-lucide-arrow-right variant: outline --- :: ### php artisan view\:cache This command caches all of the views in your application, which can greatly decrease the time it takes to render your views. ::u-button --- ariaLabel: Read more about view caching class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Read more about view caching size: md target: _blank to: https://laravel.com/docs/12.x/views#optimizing-views trailing-icon: i-lucide-arrow-right variant: outline --- :: ### php artisan event\:cache This command creates a manifest of all your application's events and listeners, which can greatly speed up the process of registering them with Laravel. ::u-button --- ariaLabel: Read more about event caching class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Read more about event caching size: md target: _blank to: https://laravel.com/docs/12.x/events#event-discovery-in-production trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Debugging the AUTORUN script It's very important to understand the nature of how containerized environments work when debugging the AUTORUN script. In some cases, some users may become frustrated when they push an update but their changes are never deployed. In most cases, this is due to a bug in their application code that causes a migration or some other process to fail. ::note If a failure occurs in the Laravel Automations script, it will exit with a non-zero exit code -- preventing the container from starting. :: If you are experiencing issues, you can enable the `AUTORUN_DEBUG` environment variable to get more detailed output of what could be going wrong. If you need even more information, you can set `LOG_OUTPUT_LEVEL` to `debug` to get **A TON** of output of what's exactly happening. ### Preventing issues with the AUTORUN script - Ensure you are running the latest version of `serversideup/php` - Ensure you have dependencies installed (ie. `composer install`) before calling this script - Use automated testing to catch issues before deploying # Task Scheduler ## Laravel Task Scheduler with Docker Run Laravel's task scheduler by using the `schedule:work` command. This runs the scheduler in the foreground and checks for scheduled tasks every minute—perfect for containers. ::tip{target="_blank" to="https://getspin.pro/docs/services/laravel-scheduler"} Want to skip the setup? [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){rel="nofollow"} handles Laravel schedulers on your VPS with Docker and zero-downtime deployments—all configured for you. :: ## Why not use cron? ::note Unlike traditional server setups, we don't use `cron` in containers. Instead, Laravel's [schedule\:work](https://laravel.com/docs/12.x/scheduling#running-the-scheduler-locally){rel="nofollow"} command keeps the process running in the foreground and checks for tasks every minute. You define the actual schedule (daily, hourly, etc.) in your Laravel application using [scheduled tasks](https://laravel.com/docs/12.x/scheduling#scheduling-artisan-commands){rel="nofollow"}. :: ## Docker Compose example This example runs a dedicated scheduler container using the same image as your web service. Use the full path to Artisan (`/var/www/html/artisan`) when defining the container command. **Key points:** - Use the same image for both your web and scheduler services - Set `SIGTERM` as the stop signal for graceful shutdown (especially for `fpm-apache` and `fpm-nginx`) - Include a health check to monitor scheduler status - Define your actual schedule times in Laravel, not in Docker ```yml [compose.yml] services: php: image: my/laravel-app task: image: my/laravel-app command: ["php", "/var/www/html/artisan", "schedule:work"] stop_signal: SIGTERM healthcheck: test: ["CMD", "healthcheck-schedule"] start_period: 10s ``` ## Defining your schedule in Laravel Configure your scheduled tasks in Laravel. The file location depends on your Laravel version: ::tabs :::tabs-item{label="Laravel 11+"} ```php [routes/console.php] daily() // Run this task once per day ->at('02:00') // At 2:00 AM ->timezone('America/Chicago'); // In the Chicago timezone Schedule::command('app:process-latefees') ->daily() // Run this task once per day ->at('04:00') // At 4:00 AM ->timezone('America/Chicago'); // In the Chicago timezone ``` ::: :::tabs-item{label="Laravel 10 and below"} ```php [app/Console/Kernel.php] command('process:invoices')->daily()->at('02:00')->timezone('America/Chicago'); $schedule->command('process:latefees')->daily()->at('04:00')->timezone('America/Chicago'); } /** * Register the commands for the application. * * @return void */ protected function commands() { $this->load(__DIR__.'/Commands'); require base_path('routes/console.php'); } } ``` ::: :: ::tip **Multiple processes in one container:** If you're running `fpm-nginx` or `fpm-apache` and you'd like to have everything in a single container, you can [write your own S6 Overlay service script](https://github.com/just-containers/s6-overlay/tree/master#writing-a-service-script){rel="nofollow"} to properly manage multiple processes in a single container. Learn more about about this in our [Using S6 Overlay guide](https://serversideup.net/open-source/docker-php/docs/guide/using-s6-overlay). :: # Queue ## Laravel Queue with Docker Run Laravel queue workers by passing the Artisan queue command as the container's command. This allows you to scale queue workers independently from your web server. ## Docker Compose example ::tip{target="_blank" to="https://getspin.pro/docs/services/laravel-scheduler"} Want to skip the setup? [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){rel="nofollow"} handles Laravel queue workers on your VPS with Docker and zero-downtime deployments—all configured for you. :: This example runs a dedicated queue container using the same image as your web service. Use the full path to Artisan (`/var/www/html/artisan`) when defining the container command. **Key points:** - Use the same image for both your web and queue services - Set `SIGTERM` as the stop signal for graceful shutdown (especially for `fpm-apache` and `fpm-nginx`) - Include a health check to monitor queue worker status ```yml [compose.yml] services: php: image: my/laravel-app queue: image: my/laravel-app command: ["php", "/var/www/html/artisan", "queue:work", "--tries=3"] stop_signal: SIGTERM healthcheck: # This is our native healthcheck script for the queue test: ["CMD", "healthcheck-queue"] start_period: 10s ``` ## Advanced configuration **Graceful shutdown:** The `SIGTERM` signal ensures queue workers finish processing current jobs before stopping. This is especially important for `fpm-apache` and `fpm-nginx` images. ::tip **Multiple processes in one container:** If you're running `fpm-nginx` or `fpm-apache` and you'd like to have everything in a single container, you can [write your own S6 Overlay service script](https://github.com/just-containers/s6-overlay/tree/master#writing-a-service-script){rel="nofollow"} to properly manage multiple processes in a single container. Learn more about about this in our [Using S6 Overlay guide](https://serversideup.net/open-source/docker-php/docs/guide/using-s6-overlay). :: # Horizon ## Laravel Horizon with Docker Run Laravel Horizon by passing the Artisan command as the container's command. Horizon provides a dashboard and monitoring for your Redis queues. ## Docker Compose example ::tip{target="_blank" to="https://getspin.pro/docs/services/laravel-scheduler"} Want to skip the setup? [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){rel="nofollow"} handles Laravel Horizon on your VPS with Docker and zero-downtime deployments—all configured for you. :: This example runs a dedicated Horizon container using the same image as your web service. Horizon requires Redis to be running and properly configured in your `.env` file. **Key points:** - Use the same image for both your web and Horizon services - Ensure Redis is running before starting Horizon - Configure Redis connection details in your `.env` file - Set `SIGTERM` as the stop signal for graceful shutdown (especially for `fpm-apache` and `fpm-nginx`) - Include a health check to monitor Horizon status ```yml [compose.yml] services: php: image: my/laravel-app redis: image: redis:6 command: "redis-server --appendonly yes --requirepass redispassword" horizon: image: my/laravel-app command: ["php", "/var/www/html/artisan", "horizon"] stop_signal: SIGTERM healthcheck: test: ["CMD", "healthcheck-horizon"] start_period: 10s ``` ## Advanced configuration **Graceful shutdown:** The `SIGTERM` signal ensures Horizon finishes processing current jobs before stopping. This is especially important for `fpm-apache` and `fpm-nginx` images. ::tip **Multiple processes in one container:** If you're running `fpm-nginx` or `fpm-apache` and you'd like to have everything in a single container, you can [write your own S6 Overlay service script](https://github.com/just-containers/s6-overlay/tree/master#writing-a-service-script){rel="nofollow"} to properly manage multiple processes in a single container. Learn more about about this in our [Using S6 Overlay guide](https://serversideup.net/open-source/docker-php/docs/guide/using-s6-overlay). :: # Reverb ## Laravel Reverb with Docker Run Laravel Reverb by passing the Artisan command as the container's command. Reverb is Laravel's WebSocket server for real-time communication. ::tip{target="_blank" to="https://getspin.pro/docs/services/laravel-scheduler"} Want to skip the setup? [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){rel="nofollow"} handles Laravel Reverb on your VPS with Docker and zero-downtime deployments—all configured for you. :: ::note Before using Reverb with Docker, follow the [Laravel Reverb setup instructions](https://laravel.com/docs/12.x/reverb){rel="nofollow"} to install and configure the Reverb package in your Laravel application. :: ## Docker Compose example This example runs Reverb as a separate container using the same image as your web service. Reverb requires a reverse proxy (like Traefik) to route WebSocket traffic correctly. **Key points:** - Use the same image for both your web and Reverb services - Reverb runs on a different port than your web server (8000 vs 8080) - Set `SIGTERM` as the stop signal for graceful shutdown (especially for `fpm-apache` and `fpm-nginx`) - Include a health check to monitor Reverb status - Configure a reverse proxy to route traffic to the correct container ```yml [compose.yml] services: php: image: my/laravel-app labels: - "traefik.enable=true" - "traefik.http.routers.laravel.tls=true" - "traefik.http.routers.laravel.entrypoints=websecure" - "traefik.http.routers.laravel.rule=Host(`https://app.example.com`)" - "traefik.http.services.laravel.loadbalancer.server.port=8080" - "traefik.http.services.laravel.loadbalancer.server.scheme=http" reverb: image: my/laravel-app command: ["php", "/var/www/html/artisan", "reverb:start", "--port=8000"] stop_signal: SIGTERM healthcheck: test: ["CMD", "healthcheck-reverb"] start_period: 10s labels: - "traefik.enable=true" - "traefik.http.routers.reverb.tls=true" - "traefik.http.routers.reverb.entrypoints=websecure" - "traefik.http.routers.reverb.rule=Host(`https://reverb.example.com`)" - "traefik.http.services.reverb.loadbalancer.server.port=8000" - "traefik.http.services.reverb.loadbalancer.server.scheme=http" ``` ## Environment variable configuration ::warning Don't confuse **CLIENT** variables with **SERVER** variables. The `REVERB_SERVER_*` variables configure the Reverb daemon itself, while `REVERB_*` variables tell your frontend clients how to connect. :: Configure these environment variables in your Laravel application: | **Laravel ENV Variable** | **Description** | **Value (matching example above)** | | ------------------------ | ------------------------------------------- | ---------------------------------- | | `REVERB_HOST` | The hostname the **CLIENT** will connect to | `reverb.example.com` | | `REVERB_PORT` | The port the **CLIENT** will connect to | `443` | | `REVERB_SCHEME` | The scheme the **CLIENT** will connect to | `https` | ## Advanced configuration ::tip **Multiple processes in one container:** If you're running `fpm-nginx` or `fpm-apache` and you'd like to have everything in a single container, you can [write your own S6 Overlay service script](https://github.com/just-containers/s6-overlay/tree/master#writing-a-service-script){rel="nofollow"} to properly manage multiple processes in a single container. Learn more about about this in our [Using S6 Overlay guide](https://serversideup.net/open-source/docker-php/docs/guide/using-s6-overlay). :: # Octane ::lead-p Laravel Octane supercharges your application's performance by keeping it loaded in memory and serving requests at incredible speeds. The FrankenPHP variation of our images provides native Octane support with worker mode built-in. :: ## What is Laravel Octane? Laravel Octane boots your Laravel application once and keeps it in memory, then processes thousands of requests without reloading. This dramatically improves performance compared to traditional PHP execution. **Traditional PHP:** Bootstrap → Handle Request → Teardown → Repeat for every request **With Octane:** Bootstrap once → Handle unlimited requests ::tip FrankenPHP is Laravel's recommended application server for Octane and is included natively in our images. No additional installation required. :: ## Quick Start Let's use this example project to get started. ::warning{target="_blank" to="https://serversideup.net/open-source/spin/docs"} This example assumes you already have a Laravel application installed. If you need help installing a new Laravel project with Docker, check out [Spin](https://serversideup.net/open-source/spin/docs){rel="nofollow"} for a simple way to get started. :: ### Classic Mode By default, FrankenPHP runs in classic mode. Your compose file might look something like this: ```yml [compose.yml] services: php: image: serversideup/php:8.5-frankenphp ports: - "80:8080" volumes: - .:/var/www/html/ ``` We'll expand upon this classic mode file and modify it to run Laravel Octane (which uses FrankenPHP's worker mode). ### Install Laravel Octane First, install Octane in your Laravel application: ```bash [Terminal] docker compose run php composer require laravel/octane ``` When that command runs, you should see a PHP file that Laravel creates in your `/public` directory. This is required for Laravel Octane to work. ```php [public/frankenphp-worker.php] ``` :: #### Use FrankenPHP's Built-in Automatic HTTPS When You Want... - To run your application and handle SSL termination all in one container - A simple setup without needing zero-downtime deployments ::note You can achieve zero-downtime deployments with FrankenPHP by placing a reverse proxy in front of the container. :: ### Bringing Your Own Certificate If automatic HTTPS isn't an option, you can provide your own certificate from a vendor like [ssls.com](https://www.ssls.com/){rel="nofollow"}. Ensure your certificate issuer provides certificates compatible with your web server in [PEM format](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail){rel="nofollow"}. To add your own certificate, mount the certificate files to the container: ::tip Set your private key file permissions to `600` (read/write for owner only). Incorrect permissions will cause errors when loading the private key. :: ::code-tree{default-value="compose.yml"} ```yml [compose.yml] {8-10,13} services: php: image: serversideup/php:8.5-fpm-nginx ports: - 80:8080 - 443:8443 environment: SSL_MODE: "mixed" SSL_PRIVATE_KEY_FILE: "/etc/ssl/custom/test-key.pem" SSL_CERTIFICATE_FILE: "/etc/ssl/custom/test.pem" volumes: - .:/var/www/html/ - ./certs/:/etc/ssl/custom/ ``` ```php [public/index.php] ``` ```pem [certs/test-key.pem] -----BEGIN PRIVATE KEY----- EXAMPLE_PRIVATE_KEY_DO_NOT_USE MIIEvQIBADANBgkqhkiG9w0BAQEFASCBKwggSjAgEAAoIBAQDExampleKeyData ThisIsNotARealPrivateKeyAndShouldNotBeUsedInProduction123456789 ReplaceThisWithYourActualPrivateKeyFile -----END PRIVATE KEY----- ``` ```pem [certs/test.pem] -----BEGIN CERTIFICATE----- EXAMPLE_CERTIFICATE_DO_NOT_USE MIIEIDCCAwigAwIBAgIQCqH+3yBp80lQ9OVmbNmbRzANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD QTAeFw0yMTA0MjkwMDAwMDBaFw0zMjA0MjgyMzU5NTlaMFsxCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAumQB+ILtbVLaKTeQeGviJLbfBxMIRZACMpbs QFmylhSTSSpLc1bNPrRVWWVmv+Lt8i3HuLjPQF+3M2NzBWVYB7Gixgd13KZBquor 2W4Sj5SfR2onVzULfBy6SrwxfSTnnykA1NAzGLbGSukNkY4fO7N4V3C1mLGvL8H -----END CERTIFICATE----- ``` :: #### Use Your Own Certificates When You... - Cannot use Let's Encrypt (corporate policy, network restrictions, etc.) - Have a specific certificate vendor requirement - Don't need zero-downtime deployments ::note You can also bring your own certificate and configure it with a reverse proxy to get zero-downtime deployments. :: ### Self-Signed Certificate ::warning Self-signed certificates will display warnings in the browser. :: While browsers will show warnings, self-signed certificates are useful for specific use cases, such as encrypting traffic between containers in a cluster. If you set `SSL_MODE` to `mixed` or `full` without providing a certificate at `$SSL_CERTIFICATE_FILE` and `$SSL_PRIVATE_KEY_FILE`, a self-signed certificate will be automatically generated. ```yml [compose.yml] {7-9} services: php: image: serversideup/php:8.5-fpm-nginx ports: - 80:8080 - 443:8443 environment: # Set SSL mode to "mixed" (HTTP + HTTPS) SSL_MODE: "mixed" volumes: - .:/var/www/html ``` The above will generate a self-signed certificate and configure the server to listen on both HTTP (port 80) and HTTPS (port 443). #### Use a Self-Signed Certificate When You... - Have a reverse proxy in front of the container handling SSL termination - Need all traffic to be encrypted (even on the internal network between containers) ::note If you have a reverse proxy in front of the container handling SSL termination, you don't need to use `SSL_MODE` at all. You can configure your reverse proxy to communicate with your PHP container via HTTP (port 8080), eliminating the need to configure SSL within the container. :: # Choosing a Host ::lead-p One of the biggest advantages of containerizing your application is the ability to run your application anywhere. This gives you the freedom to choose the best host for your needs. Here's a guide to help you choose the right host for your needs. :: ## The beauty of portability Because your application runs in a container, you're no longer tied to a specific hosting provider. The same container that runs on your laptop will run on: - Any VPS provider (DigitalOcean, Hetzner, Vultr, Linode, etc.) - Cloud platforms (AWS, Google Cloud, Azure) - Managed Kubernetes services - Your own hardware in a data center - Even a Raspberry Pi in your closet This portability means: - **No vendor lock-in** - Switch hosts without rebuilding your application - **Price shopping** - Move to cheaper providers if prices increase - **Multi-cloud strategies** - Run on multiple providers for redundancy - **Freedom to experiment** - Try different hosts without risk ## Hosting options Choose your hosting based on your team's experience, budget, and application scale. Here's a quick comparison: | Hosting Type | Best For | Monthly Cost | Management Level | When to Choose | | ---------------------- | ----------------------- | ------------ | ---------------- | ----------------------------------- | | **VPS** | Most PHP apps | $5-20 | Self-managed | You want control and best value | | **Managed Kubernetes** | Enterprise/high-traffic | $70-100+ | Moderate | You need advanced orchestration | | **Cloud Platforms** | Variable workloads | $20-50+ | Low | You want integrated cloud services | | **PaaS** | Quick deployment | $25-100+ | Minimal | You want zero infrastructure hassle | ::tip **Our recommendation:** Start with a VPS unless you have specific needs. A $10/month VPS can handle what costs $50-100/month on PaaS, and you'll have full control over your infrastructure. :: ## Recommended Hosts Below are our recommended hosts from our experience. Some links may contain referral codes, but these codes give you free credits to help you get started. These referral programs do not influence our recommendations. ### VPS Hosts ::card-group :::card --- icon: i-hosts-hetzner-square target: _blank title: Hetzner to: https://hetzner.cloud/?ref=lhLUIrkdUPhl --- Hetzner is infamously known for its low prices and high performance. They are a great choice for small to medium-sized applications. ::: :::card --- icon: i-hosts-vultr-square target: _blank title: Vultr to: https://www.vultr.com/?ref=7093917 --- Vultr has great performance and and over 32 global data centers. ::: :::card --- icon: i-hosts-digitalocean-square target: _blank title: DigitalOcean to: https://m.do.co/c/f3bad4b927ca --- DigitalOcean is most known for it's simple user experience and vast support of many developer tools. ::: :: ### PaaS Hosts ::card-group :::card --- icon: i-hosts-sevalla-square target: _blank title: Sevalla to: https://sevalla.com/ --- Sevalla is a modern PaaS with transparent usage-based pricing and no artificial limits. **Full disclosure:** Sevalla sponsors this project, but we genuinely recommend them for their refreshingly honest approach to PaaS pricing. ::: :: ## Simplifying VPS setup with Spin ::tip{target="_blank" to="https://serversideup.net/open-source/spin/"} [Spin](https://serversideup.net/open-source/spin/){rel="nofollow"} is a free and open source tool that simplifies Docker workflows from development to production. :: Spin can automatically provision and configure servers on Hetzner, DigitalOcean, and Vultr, setting up Docker Swarm for zero-downtime deployments. It handles the infrastructure complexity while you maintain full control of your server. Here's a quick example with Laravel: :iframe{allowfullscreen allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" frameBorder="0" referrerPolicy="strict-origin-when-cross-origin" src="https://www.youtube-nocookie.com/embed/5z2JoEt5XIk?si=u5v-bDN-cMv0OE-C" style="aspect-ratio: 16/9; width: 100%;" title="YouTube video player"} Spin is *not required* to run these Docker images, but it can help simplify the setup process. Spin was designed to have native integration with these images, but it can be used with any Docker setup. ::u-button --- ariaLabel: Learn more about Spin class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about Spin size: md target: _blank to: https://serversideup.net/open-source/spin/ trailing-icon: i-lucide-arrow-right variant: outline --- :: ## What to consider when choosing a host - **Budget** - VPS offers best value ($10-20/mo), PaaS most expensive ($25-100+/mo) - **Technical expertise** - VPS requires Linux knowledge, PaaS abstracts complexity - **Scale** - Small/medium apps thrive on VPS, very large apps benefit from Kubernetes - **Time investment** - PaaS saves time but costs more, VPS costs less but needs more hands-on - **Data sovereignty** - Choose providers with data centers in your required regions - **Growth trajectory** - VPS and Kubernetes scale cost-effectively, PaaS gets expensive #### Specifically for PaaS providers - **Pricing** - Is the pricing transparent and upfront? Are there alerts if you're approaching your limits? - **Debugging** - Are you able to access logs easily and debug issues? - **Vendor lock-in** - Are you building around a proprietary platform, or can you easily migrate to another provider if needed? ## Our recommendation for most projects If you're running a typical PHP application (Laravel, WordPress, custom app), here's our suggested progression: ::steps{level="4"} #### Starting out: VPS provider like Hetzner or DigitalOcean ($5-10/month) - Use Docker Swarm for single-server deployment - Simple, affordable, and plenty of room to grow #### Growing: Performance tuning and hardware upgrades ($20-40/month) - Focus on fine tuning your application for performance - Upgrade your server hardware if needed (simple as a few clicks) #### At scale: Managed Kubernetes or multiple VPS servers (>$100/month) - Move to Kubernetes when you need sophisticated orchestration - Only consider if you *must* exceed [99.999% of uptime](https://uptime.is/99.999){rel="nofollow"} :: Most PHP applications never need to move beyond a well-configured VPS. A $20/month server can serve thousands of requests per second when properly optimized. ::note Don't let fancy infrastructure distract you from building your application. Pick what works for you. Start simple, prove your concept, then scale up as needed. :: ## Key requirements for any host Regardless of which provider you choose, ensure they support: - **Docker installation** - Ability to install and run Docker (most Linux VPS hosts support this) - **Adequate resources** - At minimum 1GB RAM, 1 CPU core (2GB+ recommended for most applications) - **Security** - Strong security policies and practices to protect your application and data - **Performance** - Good performance and quality hardware to handle your application's traffic - **Network reliability** - Good uptime SLA and network performance - **Responsive support team** - Quality and responsive support team to help you out when you need it - **Data center location** - Close to your users for better performance - **Backup options** - Either provided by host or easily implemented yourself (and we strongly recommend the [3-2-1 backup rule](https://en.wikipedia.org/wiki/Backup){rel="nofollow"}) # Migrating from official PHP images ::lead-p Migrating from the official PHP images to serversideup/php is easy because our images are based on the official PHP images. We just give you a "batteries included" experience that's ready for production. :: ## Key differences ::warning Because our images run as `www-data` by default, you may need to update file permissions for mounted volumes. :: | | **Official PHP Images** | **serversideup/php** | | -------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Base Operating System | Debian, Alpine | Debian, Alpine | | PHP Compilation | PHP Source Code | PHP Source Code (based on official PHP images) | | Run PHP, pinned to the minor version | ✅ | ✅ | | Multi-arch support | ✅ | ✅ | | Init System | Docker CMD | Docker CMD or [S6-Overlay](https://github.com/just-containers/s6-overlay){rel="nofollow"} | | Published Registry | DockerHub | [DockerHub](https://hub.docker.com/r/serversideup/php){rel="nofollow"}, [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php){rel="nofollow"} | | Unprivileged by default | ❌ | ✅ | | Variable-first configuration | ❌ | ✅ | | Includes `composer` | ❌ | ✅ | | Includes [`install-php-extensions`](https://github.com/mlocati/docker-php-extension-installer){rel="nofollow"} | ❌ | ✅ | | Production-Ready by default | ❌ | ✅ | | Built-in security optimizations | ❌ | ✅ | | Optimized for Laravel & WordPress | ❌ | ✅ | | NGINX + FPM variation | ❌ | ✅ | | FrankenPHP variation | ❌ | ✅ | | Native health checks | ❌ | ✅ | ::u-button --- ariaLabel: Learn more about choosing an image class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about choosing an image size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/choosing-an-image trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Making the change Making the change will literally take you two seconds. ::steps{level="4"} #### Figure out which image you'd like to use Review our [choosing an image](https://serversideup.net/open-source/docker-php/docs/getting-started/choosing-an-image) guide to help you decide which image you'd like to use. Also, make sure our [default configurations](https://serversideup.net/open-source/docker-php/docs/getting-started/default-configurations) satisfy your requirements. #### Update your `Dockerfile` or `compose.yml` file :::tip We simply change `php:8.5-apache` to `serversideup/php:8.5-fpm-apache` ::: **Dockerfile** :::code-group ```dockerfile [ORIGINAL: Dockerfile] {1} FROM php:8.5-apache # Rest of your Dockerfile... ``` ```dockerfile [UPDATED: Dockerfile] {1} FROM serversideup/php:8.5-fpm-apache # Rest of your Dockerfile... ``` ::: **compose.yml** :::code-group ```yml [ORIGINAL: compose.yml] {3,5-6} services: php: image: php:8.5-apache ports: - 80:80 - 443:443 ``` ```yml [UPDATED: compose.yml] {3,5-6} services: php: image: serversideup/php:8.5-fpm-apache ports: - 80:8080 - 443:8443 ``` ::: #### Test your application Make sure to test your application to ensure it's working as expected. #### Deploy and enjoy! Making the change is that simple. :: ## Related resources If you need to customize the base image, review our guides below: - [Changing common PHP settings](https://serversideup.net/open-source/docker-php/docs/customizing-the-image/changing-common-php-settings) - [Installing additional PHP extensions](https://serversideup.net/open-source/docker-php/docs/customizing-the-image/installing-additional-php-extensions) - [Startup scripts](https://serversideup.net/open-source/docker-php/docs/customizing-the-image/adding-your-own-start-up-scripts) # Using Healthchecks With Laravel ## Native Docker Health Checks for Laravel ::lead-p Dialing in health checks are very important for ensuring your application is running smoothly and that you're able to deploy updates without any downtime. This guide will explain how our health checks work and how you can use them to your advantage. :: :iframe{allowfullscreen allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" frameBorder="0" referrerPolicy="strict-origin-when-cross-origin" src="https://www.youtube-nocookie.com/embed/cuYIB5VrH1Q?si=75VBCKAe5x2Hmckd" style="aspect-ratio: 16/9;" title="YouTube video player"} ## What are Health Checks? Health checks are a way to check the status of your application. Whenever a container is started, a health check is performed. If the health check fails, the container will be restarted or marked as unhealthy. Health checks are very important for rolling updates and ensuring your application can start up in order if you have services that depend on each other. ## Our Health Checks We offer a number of health check commands, specifically for Laravel. You can find these commands are prefixed with `healthcheck-` and are located in [`/usr/local/bin`](https://github.com/serversideup/docker-php/tree/main/src/common/usr/local/bin){rel="nofollow"}. The examples below show how to use these health checks in your `compose.yml` file, but you can also use them in other environments. ## Default Health Check Settings By default, our Dockerfiles ship with the following health check commands: | Variation | Health Check Command | | ------------ | ------------------------------------------------------------------------------------------------ | | `cli` | (none) | | `fpm` | [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){rel="nofollow"} | | `fpm-apache` | `curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH` | | `fpm-nginx` | `curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH` | | `frankenphp` | `curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH` | ::note Notice how `fpm-apache`, `fpm-nginx`, and `frankenphp` have a `$HEALTHCHECK_PATH` variable? This is because you can specify a path for the health check to validate. :: ## Changing the Health Check Path By default, you can see our [Environment Variable Specification](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) shows the `HEALTHCHECK_PATH` variable is set to `/healthcheck`. ::tip This only validates that FPM-NGINX or FPM-APACHE are running and ready to accept connections. It does not validate that Laravel is running or healthy. :: If you are using Laravel, [modern versions of Laravel will ship with a `/up` route](https://laravel.com/docs/12.x/deployment#the-health-route){rel="nofollow"} that you can use to validate that Laravel is running and healthy. You can even create your own custom path in your application if you want. As long as the path returns a 200 status code, the health check will be successful. ## Advanced Laravel Services Since it is good practice to use the same Docker image for all our services, we also make additional health checks for Laravel's advanced services. If you're looking for rolling updates with zero downtime, it's very important to use these health checks. ### Laravel Horizon We utilize the `artisan horizon:status` command to check the status of Laravel Horizon. This is a command native to Laravel Horizon and is used to determine if the Horizon process is running. To run this command automatically, you can call our health check command in your `compose.yml` file. ```yml [compose.yml] ## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-horizon"] ``` [See a full example of configuring Laravel Horizon →](https://serversideup.net/open-source/docker-php/docs/framework-guides/laravel/horizon) ### Laravel Reverb We use `pgrep` to check if the `reverb:start` command is running. This ensures the Reverb process is running and ready to accept connections. To run this command automatically, you can call our health check command in your `compose.yml` file. ```yml [compose.yml] ## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-reverb"] ``` [See a full example of configuring Laravel Reverb →](https://serversideup.net/open-source/docker-php/docs/framework-guides/laravel/reverb) ### Laravel Scheduler We use `pgrep` to check if the `schedule:work` command is running. This ensures the scheduler process is running. To run this command automatically, you can call our health check command in your `compose.yml` file. ```yml [compose.yml] ## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-schedule"] ``` [See a full example of configuring Laravel Scheduler →](https://serversideup.net/open-source/docker-php/docs/framework-guides/laravel/task-scheduler) ### Laravel Queue We use `pgrep` to check if the `queue:work` command is running. This ensures the queue process is running. To run this command automatically, you can call our health check command in your `compose.yml` file. ```yml [compose.yml] ## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-queue"] ``` [See a full example of configuring Laravel Queue →](https://serversideup.net/open-source/docker-php/docs/framework-guides/laravel/queue) # Using S6 Overlay ## What's S6 Overlay? [S6 Overlay](https://github.com/just-containers/s6-overlay){rel="nofollow"} is a process supervisor designed for containerization from the ground up. It's a modern alternative to [Supervisor (aka Supervisord)](https://supervisord.org/){rel="nofollow"}. S6 Overlay is a perfect match for running PHP because it usually requires running multiple processes together. ## What images do you use S6 Overlay with? We only use S6 Overlay for images that need two processes to run a service. This includes: - `serversideup/php:*-fpm-apache` - `serversideup/php:*-fpm-nginx` ## Why does PHP need multiple processes? Serving a PHP application can be broken down into two different components: 1. The PHP Application Itself 2. The static files that go with it (JavaScript, images, etc) There are ways to have these components served together with Apache modules like "mod\_php". As time moved on, we found this to be very resource inefficient. Running "mod\_php" with Apache meant that even when a JavaScript file needs to be loaded, Apache would load PHP to serve that file. This caused a lot of unnecessary memory and CPU usage for files that didn't need PHP to be served. ### PHP-FPM PHP-FPM was the answer to make this serving a PHP application more efficient. This allowed PHP apps to be served with a lot less memory and CPU overhead. Although this method was more efficient, it meant we still need something to serve our static content. This is where we turned to "reverse proxies". ### Reverse proxy Reverse proxies are servers that route traffic based on the request. To make things more confusing, you can have a web server be a reverse proxy and a web server at the same time (this is how we run NGINX). You can even configure Apache to run as a web server and a reverse proxy too (we run our `php:*-fpm-apache` images like this). ![Reverse Proxy Diagram](https://serversideup.net/open-source/docker-php/images/docs/reverse-proxy.svg) In the example above, you can see the web request coming in from the top. NGINX is our first stop where it inspects the request. #### Static Files If the request is for a static file (jpeg, js, png, etc), NGINX will also serve that file for us without loading PHP-FPM. This makes the request very fast and efficient. #### PHP Files If the request ends in `.php`, then it will send the request over to PHP-FPM. PHP-FPM will then execute the PHP file and return a response to the original client, all passed through NGINX. ## Shouldn't containers only have one process? In a perfect world, that would be ideal -- but this isn't always realistic. You can see in the example above we need two things running: 1. NGINX: To serve static files 2. PHP-FPM: To serve PHP application If you want to replicate your application without the added complexity of multiple physical servers, etc -- you need something like S6 Overlay to properly bring up the processes and ensure your application service health is accurately reported. [S6 Overlay's philosophy](https://github.com/just-containers/s6-overlay#the-docker-way){rel="nofollow"} is a perfect match when it comes to running PHP: - A container should do ***one thing*** (which may contain multiple processes). When that one thing stops, the container should also stop. ## The advantages of S6 Overlay When we configure PHP to run with the S6 Overlay system, we get a number of advantages: - ✅ S6 Overlay was designed from the ground up to run within containers - ✅ We get explicit control to run small scripts or configurations before/after the main processes start - ✅ We get a better confidence on answering "Is my container actually healthy?" ## This disadvantage of S6 Overlay - ❌ S6 Overlay may not be compatible with all PaaS providers, depending on how they run their containers ([see this comment from the S6 Overlay creator](https://github.com/just-containers/s6-overlay/issues/535#issuecomment-1597680218){rel="nofollow"}) ## S6 Overlay vs. Supervisor Many people flock to Supervisor, which was a very popular option before containerization. Here's some examples why you may want to trade Supervisor for S6 Overlay: ### How Supervisor reports container health ![Supervisor Container Health Example](https://serversideup.net/open-source/docker-php/images/docs/supervisor-container.svg) When you bring up Supervisord within a container, it will be assigned `PID 1`. Then Supervisor will bring up child processes with it. During a failure, Supervisor can be configured to restart the child process to attempt recovery, but the container orchestrator thinks the container is still healthy because `supervisord` is occupying `PID 1` which is still healthy. **👉 This design can lead to inaccurate container health statuses during a failure.** ### How S6 Overlay reports container health ![S6 Overlay Container Health Example](https://serversideup.net/open-source/docker-php/images/docs/s6-overlay-container.svg) S6 Overlay was designed to be run in containers from the ground up. S6 Overlay can also attempt recovery, but it is more accurate on determining container health compared to Supervisor. **👍 By design, S6 Overlay can accurately detect a failure and exit (which is what we want when our app fails).** ## Customizing the initialization process ![Container Initialization Example with S6 Overlay](https://serversideup.net/open-source/docker-php/images/docs/container-init.svg) Since S6 Overlay was designed around the idea of containerization, there are also a number of other advantages to properly time your customizations during container startup. S6 Overlay has a number of options to [write our own service script](https://github.com/just-containers/s6-overlay/tree/master#writing-a-service-script){rel="nofollow"} and properly time everything. In the example above, you can see we have a `runas-user` script which helps us customize and set custom UIDs and GIDs for our file permissions. At the same time, `laravel-automations` executes to see if there are any automated migrations to run. Both scripts must finish successfully before S6 Overlay starts our main `php-fpm` process, which has both scripts listed as a dependency. As you can see this structure can be very powerful in making your own customizations. This is great for giving you full control of how you'd like your application to behave. ::u-button --- ariaLabel: Changing Common PHP Settings class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Changing Common PHP Settings size: md to: https://serversideup.net/open-source/docker-php/docs/customizing-the-image/changing-common-php-settings trailing-icon: i-lucide-arrow-right variant: outline --- :: # Understanding File Permissions ::lead-p Working with file permissions is one of the biggest headaches when working with PHP + Docker. This generally is because the PHP server also requires a web server to serve static files. By default, this means multiple users are created in the container, and permissions can get out of hand quickly. :: ![Traditional PHP File Permissions Configuration](https://serversideup.net/open-source/docker-php/images/docs/permissions-privileged.png){:zoom='false'} ## Even more frustrating: Development Environments Even if someone configured a single user in the container to run both the PHP server and the web server, things get even more complicated in development environments. For example, if you have Alice running her Windows Machine with WSL2, she might have a user ID of `1001`. Then you have Bob running his Ubuntu workstation with a user ID of `1002`. Meanwhile, Charlie is running his Docker on his macOS machine (that runs a tiny VM) that has a totally different file permission experience compared to Windows and Linux because of the file system differences. If a volume is mounted from the container to the host, the container will write files to the host as `33:33`, which will require sudo/root permissions to edit and delete files. ## Our industry attempted workarounds We've seen experiences that allow users to provide an environment variable of `PUID` and `PGID`. Although this is a great user experience, it requires the container user to be privileged, which is a major "no-no" in the security world. It also had downstream file permission errors if the container failed on initialization where logs would be created by the root user and no longer writable by the `www-data` user. ## Our solution We focus on providing the tools to give sysadmins the ability to: - Keep their containers unprivileged by default - Allow the dynamic reconfiguration of the container user and group ID (at build time only) It's a bummer that we can only set the user and group ID at build time, but it's a small price to pay for the security benefits of running unprivileged containers. #### How it works - By default, all our images run `www-data` as the user (`33:33` for Debian and `82:82` for Alpine) - We provide a script that can be called at build time to change the UID and GID of `www-data` (called `docker-php-serversideup-set-id`) - If you need to update permissions of service files (example: NGINX, Apache, FrankenPHP, etc), you can run the `docker-php-serversideup-set-file-permissions` at build. This will automatically detect the service and update the file permissions accordingly. - We will use a multi-stage build to ensure that the `docker-php-serversideup-set-id` script is not executed in the construction of the final image ## Example Here's an example of ensuring our UID/GID of `www-data` will match the development UID/GID of the host machine, while preserving the default UID/GID of `33:33` for the final image: ```dockerfile [Dockerfile] ############################################ # Base Image ############################################ FROM serversideup/php:8.5-fpm-nginx-bookworm AS base ############################################ # Development Image ############################################ FROM base AS development # Switch to root so we can do root things USER root # Save the build arguments as a variable ARG USER_ID ARG GROUP_ID # Use the build arguments to change the UID # and GID of www-data while also changing # the file permissions for NGINX RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ \ # Update the file permissions to match the new UID/GID docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID # Drop back to our unprivileged user USER www-data ############################################ # Production Image ############################################ # Since we're calling "base", production isn't # calling any of that permission stuff FROM base AS production # Copy our app files as www-data (33:33) COPY --chown=www-data:www-data . /var/www/html ``` To show a simple Docker Compose file example for development, we could use: ```yml [compose.yml] services: php: build: context: . target: development args: # UID and GID must be set as environment variables on the host machine USER_ID: $UID GROUP_ID: $GID ports: - 80:8080 volumes: - .:/var/www/html ``` When we run `docker compose up`, our compose file directs us to build the `development` target. This target will run the `docker-php-serversideup-set-id` script to change the UID and GID of `www-data` to match the host machine (assuming `$UID` and `$GID` are set in a zsh/bash profile or something similar). This will allow us to run the container as an unprivileged user while still having the correct permissions to read and write files. The best thing is the user can delete files off of their machine without being prompted for sudo permissions. This is because we're aligning the UID/GID of the container with the host machine. When it comes to building our image for production, we just use the `production` target, which will copy the files as `www-data` with the default UID/GID of `33:33`. ## An optimized experience from development to production If you like the concepts above and you're looking for an optimized experience for developers (especially when it comes simplifying the setting of UID/GID), we recommend checking out our other open source project Spin. Spin is a lightweight wrapper for Docker Compose that allows you to manage your environment from development to production. ::u-button --- ariaLabel: Learn more about Spin class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about Spin size: md to: https://serversideup.net/open-source/spin/ trailing-icon: i-lucide-arrow-right variant: outline --- :: # Major version migrations ::lead-p When we ship a new major version of `serversideup/php`, we collect the breaking changes, new features, and migration checklists in this guide. Use it whenever you're crossing a major version boundary — for example, V3 → V4 or V2 → V3. For day-to-day patches and security updates, see the [Upgrade Guide](https://serversideup.net/open-source/docker-php/docs/getting-started/upgrade-guide) instead. :: ## Version 3 → Version 4 Migration Version 3 to Version 4 is a much easier migration compared to previous versions. There are **no breaking changes**, so you can simply update your image tag to the latest version and take advantage of the new features. ### New Features in Version 4 This release focused on expanding image variations, improving Laravel automations, and enhancing the developer experience. Here are the key features: - **FrankenPHP variation** - A new production-ready FrankenPHP variation with intelligent defaults, flexible environment configuration, native health checks, and support for Debian and Alpine operating systems. - **Revamped documentation site** - Completely rewritten documentation with improved navigation, better examples, and a modern user experience. - **Enhanced Laravel automations** - Refactored to use `php artisan optimize` by default (following Laravel best practices), with support for migration modes (`fresh`, `refresh`), database connection selection, seeding options, and easier debugging with `AUTORUN_DEBUG`. - **Expanded environment variables** - 25+ new environment variables for fine-tuning PHP, NGINX, Apache, and FrankenPHP configurations. [See the full list of environment variables →](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) - **Improved health checks** - Better container startup detection using `start-period` and `start-interval` for more accurate health readings. - **IPv6 support for NGINX** - Control IP listening protocols with `NGINX_LISTEN_IP_PROTOCOL` (supports `ipv4`, `ipv6`, or `all`). - **Enhanced file permissions script** - `docker-php-serversideup-set-file-permissions` now includes automated service detection and support for multiple directories with the `--dir` flag. - **Quieter logs** - Health check requests no longer appear in access logs for `fpm-nginx` and `fpm-apache` variations. ### Quality of Life Improvements - **Startup scripts** - Improved handling of `entrypoint.d` scripts with better error handling and a redesigned container startup info display. - **FPM process control** - Default changed to `ondemand` for even lower resource usage in `fpm-nginx` and `fpm-apache` variations. - **Better Apache logs** - Access logs now include "Referer" and "User Agent" for better debugging. - **NGINX improvements** - Added `absolute_redirect off;` for better proxy compatibility, fixed `svgz` handling with Symfony's asset mapper, and allowed `robots.txt` to be dynamically generated by PHP. ### V4 Migration Checklist Since there are no breaking changes, the migration is straightforward: #### Update Your Images Simply update your image tags to the latest version. For example: ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-nginx ``` No other changes are required unless you want to take advantage of new features. #### Optional: Leverage New Features **Consider enabling Laravel optimizations (if using Laravel):** ```yml [compose.yml] services: php: image: serversideup/php:8.5-fpm-nginx environment: AUTORUN_ENABLED: "true" AUTORUN_LARAVEL_OPTIMIZE: "true" ``` **Try the new FrankenPHP variation:** ```yml [compose.yml] services: php: image: serversideup/php:8.5-frankenphp ports: - 80:8080 - 443:8443 ``` That's it! Version 4 is designed to be a smooth, non-breaking upgrade that gives you more flexibility and features when you need them. ## Version 2 → Version 3 Migration If you're an existing user of our v2 images, be sure that your current configurations are NOT set to use the latest images. To do this, you can lock your images into the `v2.2.1` tag. This will ensure that you're not automatically upgraded to the v3 images. For example, if you are using `8.2-fpm-nginx`, you would change your `compose.yml` file to use the [`v2.2.1`](https://hub.docker.com/r/serversideup/php/tags?page=1\&name=2.2.1){rel="nofollow"} tag: ```yml [compose.yml] {3} services: php: image: serversideup/php:8.2-fpm-nginx ports: - 80:80 volumes: - .:/var/www/html ``` ```yml [compose.yml] {3} services: php: image: serversideup/php:8.2-fpm-nginx-v2.2.1 ports: - 80:80 volumes: - .:/var/www/html ``` All you need to do is add `-v2.2.1` to the end of the image tag. This will ensure that you're not automatically upgraded to the v3 images. ### New Features in Version 3 We've been busy overhauling our PHP Docker Images to make them more production-ready and easier to use. Here are some of the new features we've added: - **Based on official PHP Images** - We're now building an improved developer experience on top of the official PHP Docker images. - **Unprivileged by default** - We're now running our images as an unprivileged user by default. This is a huge step forward in security and compatibility. - **PHP 8.4 support** - We're now shipping the latest and greatest. - **Pin to the exact minor version** - Pin your app to the exact minor version of PHP that you want to use. This means you can pin to `8.2.12` instead of `8.2`. - **Easier start up script customization** - We now have a folder called `/etc/entrypoint.d` that allows you to easily customize your container with scripts. Just put them in numerical order and we'll execute any shell script you want. No S6 Overlay knowledge required. - **Expanded Laravel Automations** - We added automations to run `config:cache`, `route:cache`, `view:cache`, `event:cache`, `migrate --force --isolated`, and `storage:link` - **NGINX Unit Support** - We're offering NGINX Unit as a variation as an alternative to PHP-FPM. This allows you to run PHP applications without the need for a webserver like NGINX or Apache to run with PHP-FPM. - **Available on GitHub Packages** - We're now publishing our images to GitHub Packages. This means you can use our images without needing to authenticate with Docker Hub. ### Breaking changes in Version 3 ::caution The following changes are considered to be "breaking changes" and will require you to make changes to your application. :: #### Ubuntu is no longer used as a base image We now use Debian or Alpine as our base OS (because we're using the official PHP images as a base). This is a huge change, but we're confident this will be the best direction moving forward. #### `ppa:ondrej/php` is no longer used Since we're using PHP.net as the "official source of truth" for getting our PHP versions, this means we're also dropping support for the `ppa:ondrej/php` repository. If you're using things like `apt-get install php-redis` you will need to change your method of installing PHP extensions. [Learn how to install your own PHP extension →](https://serversideup.net/open-source/docker-php/docs/customizing-the-image/installing-additional-php-extensions) #### `webuser` is no longer being used We used to add a user called `webuser` with the UID of `9999` with shell permissions. To increase security, we're now using the `www-data` user and group that is built into the official PHP images. If you have mounted volumes, you will need to `chown` the files to match the ID of the `www-data` user and groups. For Debian, this is `33:33` and for Alpine, this is `82:82`. #### NGINX and Apache listen on 8080 (HTTP) and 8443 (HTTPS) by default Our images are now unprivileged by default. This is a major step forward in security and compatibility. Since we are unprivileged by default, we lose the ability to mount on ports less than 1024. If you're using NGINX or Apache, you will need to update your port mappings to use `8080` and `8443` instead of `80` and `443`. [Learn more about this change →](https://serversideup.net/open-source/docker-php/docs/getting-started/default-configurations#unprivileged-by-default) #### S6 Overlay is only used in `*-fpm-apache` and `*-fpm-nginx` images Due to compatibility issues, we only use S6 Overlay in our `*-fpm-apache` and `*-fpm-nginx` images. If you were using S6 Overlay for our other variations (cli, fpm, etc), you will need to migrate your scripts to use the new `/etc/entrypoint.d` folder. #### `SSL_MODE` is now set to `off` by default (HTTP only) Running end-to-end SSL by default created more problems than good. By default, we're now shipping HTTP-only by default with the option for people to turn this on. #### `AUTORUN_ENABLED` is now set to `false` by default. Having this set to "true" by default also created more problems than good. If you want to use any of the Laravel Automation Scripts, be sure to set this to `true`. #### MSMTP is no longer included in the images For security and image size reasons, we removed MSMTP from the images. If you need to send emails, use an external SMTP service like Postmark/Sendgrid/Mailgun. You can also extend the image yourself to include MSMTP specifically for your use case. #### Variable deprecations - `WEB_APP_DIRECTORY` has now been renamed to `APP_BASE_DIR` - `DEBUG_OUTPUT` has been removed for in favor of `LOG_OUTPUT_LEVEL=debug` - `PUID` & `PGID` are no longer used because it requires root privileges. See the [new way to set the UID and GID →](https://serversideup.net/open-source/docker-php/docs/guide/understanding-file-permissions) - `MSMTP_RELAY_SERVER_HOSTNAME` & `MSMTP_RELAY_SERVER_PORT` are no longer used because MSMTP is no longer included in the images. - `PHP_POOL_NAME` has been renamed to `PHP_FPM_POOL_NAME` ### V3 Migration Checklist Here is a good list to perform the V3 migration. #### Repository - Ensure you're committing to a test environment #### Docker Compose - Update the image name (if applicable) - Check each environment variable exists and is set to a proper value [See the full list of environment variables →](https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification) - Ensure you updated the ports to `8080` and `8443` for NGINX, Apache, and Unit - Consider adding `PHP_OPCACHE_ENABLE=1` to your production environment for increased performance #### Dockerfile - Update the base image name (if applicable) - Remove any `ppa:ondrej/php` references - Remove any Ubuntu specific commands - Ensure all extensions are installed with the `install-php-extensions` command [Learn how to install your own PHP extension →](https://serversideup.net/open-source/docker-php/docs/customizing-the-image/installing-additional-php-extensions) - Ensure your `COPY` commands are copying with the correct permissions (i.e. `--chown=www-data:www-data`) #### CI/CD If you're running `fpm-nginx` (or similar) on a runner that's running as your builds as `root`, you may need to add `user = www-data` and `group = www-data` to your `php-fpm.conf` file so you can bring FPM up correctly. If you have to run things as root in CI, you can do this with a multi stage build and set the targets: ```dockerfile [Dockerfile] ############################################ # Base Image ############################################ # Learn more about the Server Side Up PHP Docker Images at: # https://serversideup.net/open-source/docker-php/ FROM serversideup/php:8.4-fpm-nginx AS base ## Uncomment if you need to install additional PHP extensions # USER root # RUN install-php-extensions bcmath gd ############################################ # Development Image ############################################ FROM base AS development # We can pass USER_ID and GROUP_ID as build arguments # to ensure the www-data user has the same UID and GID # as the user running Docker. ARG USER_ID ARG GROUP_ID # Switch to root so we can set the user ID and group ID USER root RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID USER www-data ############################################ # CI image ############################################ FROM base AS ci # Sometimes CI images need to run as root USER root ############################################ # Production Image ############################################ FROM base AS deploy COPY --chown=www-data:www-data . /var/www/html USER www-data ``` #### Production/Staging Servers - Update all host volume file permissions to match the `www-data` UID/GID (`33:33` for Debian, `82:82` for Alpine) [Learn how to manage file permissions](https://serversideup.net/open-source/docker-php/docs/guide/understanding-file-permissions) - If you're running Docker Swarm with host volume mounts, we created a script that could potentially help ([change-volume-permissions.sh](https://github.com/serversideup/docker-volume-change-permission-script){rel="nofollow"}) #### Deployment - CI/CD with valid tests is always encouraged - After completing all steps above, you're now ready to deploy the new images # Changing php.ini settings ::lead-p Instead of going through the effort of writing custom scripts or mounting files to change PHP settings, have the power to change common settings with the simplicity of an environment variable. :: ## Common Examples All our environment variables are documented and can be found in our environment variable specification documentation. ::u-button --- ariaLabel: Environment Variable Specification class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Environment Variable Specification size: md to: https://serversideup.net/open-source/docker-php/docs/reference/environment-variable-specification trailing-icon: i-lucide-arrow-right variant: outline --- :: Here are a few examples on how you can change common PHP settings. ```yml [compose.yml] {4-6} services: php: image: serversideup/php:8.2.12-fpm-nginx-bookworm environment: PHP_POST_MAX_SIZE: "500M" PHP_UPLOAD_MAX_FILE_SIZE: "500M" SSL_MODE: "mixed" ports: - 80:8080 - 443:8443 volumes: - .:/var/www/html/ ``` You can also adjust environment variables using the Docker CLI. ```bash [Terminal] docker run -d \ -p 80:8080 \ -v $(pwd):/var/www/html \ -e PHP_DATE_TIMEZONE="America/New_York" \ serversideup/php:8.2.12-fpm-nginx-bookworm ``` ## Setting your own php.ini PHP will read the `php.ini` file from the `/usr/local/etc/php/conf.d/` directory in alphabetical order. This means you can create your own `php.ini` file and mount it to the container to override the default settings. For example, we can create this file in our project directory: ```ini [zzz-custom-php.ini] mysqli.max_persistent = 300 opcache.max_file_size = 10M opcache.log_verbosity_level = 3 ``` Then in our Dockerfile, we can copy this file to the `/usr/local/etc/php/conf.d/` directory: ```dockerfile [Dockerfile] FROM serversideup/php:8.5-fpm-nginx-bookworm COPY zzz-custom-php.ini /usr/local/etc/php/conf.d/ ``` If you prefer to remove the default `php.ini` file, you can do so by adding the following line to your Dockerfile: ```dockerfile [Dockerfile] FROM serversideup/php:8.5-fpm-nginx-bookworm RUN rm /usr/local/etc/php/conf.d/serversideup-docker-php.ini COPY zzz-custom-php.ini /usr/local/etc/php/conf.d/ ``` ## Validating changes It's always best to validate your changes by running `php -i` via the command line or using [`phpinfo()`](https://www.php.net/manual/en/function.phpinfo.php){rel="nofollow"}. # Installing PHP extensions ::lead-p serversideup/php includes the [`install-php-extensions`](https://github.com/mlocati/docker-php-extension-installer){rel="nofollow"} tool by default. This tool allows you to install almost any PHP module that you'll need. :: ## Default extensions By default, we include a number of PHP extensions to get you up and running. You can learn more why we have certain defaults and what's all included on our default configurations page. ::u-button --- ariaLabel: Learn more about default extensions class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about default extensions size: md to: https://serversideup.net/open-source/docker-php/docs/getting-started/default-configurations#default-php-extensions trailing-icon: i-lucide-arrow-right variant: outline --- :: ## What extensions are supported? Since we're using [`install-php-extensions`](https://github.com/mlocati/docker-php-extension-installer){rel="nofollow"}, we have a wide support of extensions across many versions of PHP. You can find the full list of supported extensions on the project's README. ::u-button --- ariaLabel: View the supported extensions class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: View the supported extensions size: md target: _blank to: https://github.com/mlocati/docker-php-extension-installer#supported-php-extensions trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Installing extensions Once you have your extensions ready for installation, you need to use `root` permissions to install them. In most cases, the best experience is to use a `Dockerfile` to do this while you package your application in a container. If you're not familiar with the concept of packaging your application for deployment, we recommend you to read our guide on how to do it. ::u-button --- ariaLabel: Learn more about packaging your application for deployment class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about packaging your application for deployment size: md to: https://serversideup.net/open-source/docker-php/docs/deployment-and-production/packaging-your-app-for-deployment trailing-icon: i-lucide-arrow-right variant: outline --- :: ### Preparing your Dockerfile ::warning **Our images are unprivileged by default.** This means you'll need to switch to `root` to do "root things", then switch back to the `www-data` user. This ensures your container image is hardened against security vulnerabilities. :: ```dockerfile [Dockerfile] # Choose our base image FROM serversideup/php:8.5-fpm-nginx # Switch to root so we can do root things USER root # Install the intl and bcmath extensions with root permissions RUN install-php-extensions intl bcmath # Drop back to our unprivileged user USER www-data ``` ## Building images with Docker Compose Here's a simple example with Docker Compose that builds an image with the `intl` and `bcmath` extensions. ::code-tree{default-value="Dockerfile"} ```dockerfile [Dockerfile] # Choose our base image FROM serversideup/php:8.5-fpm-nginx # Switch to root so we can do root things USER root # Install the intl and bcmath extensions with root permissions RUN install-php-extensions intl bcmath # Drop back to our unprivileged user USER www-data ``` ```yml [compose.yml] services: php: # Use "build" instead of "image" build: # Use the Dockerfile in the current directory context: . dockerfile: Dockerfile # Expose localhost:80 to NGINX's port 8080 ports: - 80:8080 # Mount current directory to /var/www/html volumes: - ./:/var/www/html ``` ```php [public/index.php] ``` :: Once we have our project ready, we can bring our container up with: ::tip We use the `--build` flag to tell Docker to rebuild the image from scratch. Good practice in development if you're making changes to your Dockerfile. :: ```bash [Terminal] docker compose up --build ``` ## Real-life example showing development to production If you're looking for a more realistic example how this looks from development to production, check out our guide below. ::u-button --- ariaLabel: Learn more about development to production class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about development to production size: md to: https://serversideup.net/open-source/docker-php/docs/deployment-and-production/development-to-production trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Common PHP extensions that you might need We compiled a list of extensions for you to reference. | Extension | Description | Why it's not included by default | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | [intl](https://www.php.net/manual/en/intro.intl.php){rel="nofollow"} | Internationalization functions, [used by Laravel for validating emails](https://laravel.com/docs/10.x/validation#rule-email){rel="nofollow"} with "DNS" or "spoof" validation. | Our tests showed this module will add about 40 MB of space to the Docker image, so we decided to not include it by default. | ::tip --- target: _blank to: https://github.com/serversideup/docker-php/discussions/new?category=q-a --- Don't see the extension you need? Having trouble? [Open a discussion on GitHub →](https://github.com/serversideup/docker-php/discussions/new?category=q-a){rel="nofollow"} :: # Adding Start Up Scripts ::lead-p We provide a few [default entrypoint scripts](https://serversideup.net/open-source/docker-php/docs/getting-started/default-configurations#default-entrypoint-scripts) to get you going, but sometimes you want to just add your own. We've made it easy to do that with our `entrypoint.d` directory. :: ## Entrypoint Script Requirements ::note Before you write your entry point script, be aware of the following requirements. Your script should: - Be executable (755 permissions) - Located in the `/etc/entrypoint.d` directory - Have the file extension ending in `.sh` :: ::tip We recommend writing your script in `/bin/sh` for the best compatibility between Alpine and Debian. If you choose to use `/bin/bash`, your script will only be able to run on Debian-based images. :: ## Choose your execution order Since [we provide default entrypoint scripts](https://serversideup.net/open-source/docker-php/docs/getting-started/default-configurations#default-entrypoint-scripts), you may want to choose the order in which your scripts are executed. We've made it easy to do that by prefixing your script with a number. The lower the number, the earlier it will be executed. ::tip If you want to disable our entrypoint scripts, you can set `DISABLE_DEFAULT_CONFIG` to `true` in your environment variables. :: ## Long running services ::note Don't use entrypoint scripts for long-running services. You want your services to be monitored and restarted if they crash. :: Anything in the `/etc/entrypoint.d` directory are scripts that are intended to run quickly and then move on. If you run a service as an entrypoint script, that service may crash and not be restarted. Instead, learn about [using S6 overlay](https://serversideup.net/open-source/docker-php/docs/guide/using-s6-overlay) so your services can be properly initialized and monitored. See the [S6 Overylay project](https://github.com/just-containers/s6-overlay){rel="nofollow"} for more details on how to write your own S6 service. ## Example: Create a custom entrypoint script In this example, let's create a `99-my-script.sh` so it executes after all the other default scripts. ::code-tree{default-value="Dockerfile"} ```bash [entrypoint.d/99-my-script.sh] #!/bin/sh echo "👋 Hello, world!" ``` ```dockerfile [Dockerfile] FROM serversideup/php:8.5-fpm-nginx # Copy our scripts as executable COPY --chmod=755 ./entrypoint.d/ /etc/entrypoint.d/ ``` ```yml [compose.yml] services: php: build: context: . dockerfile: Dockerfile ports: - 80:8080 volumes: - .:/var/www/html ``` ```php [public/index.php] ``` :: In the example above, you can see in the `Dockerfile` we are copying our `entrypoint.d` directory to `/etc/entrypoint.d/` in the container. We're also setting the permissions to `755` so our scripts are executable. ## Running our example When we run `docker compose up`, we can confirm our script is executing by checking the logs: ::code-panel{label="Output of "docker compose up""} ```txt php-1 | 👉 [NOTICE]: Improve PHP performance by setting PHP_OPCACHE_ENABLE=1 (recommended for production). php-1 | (init-webserver-config): Processing /etc/nginx/nginx.conf.template → /etc/nginx/nginx.conf... php-1 | (init-webserver-config): Processing /etc/nginx/site-opts.d/http.conf.template → /etc/nginx/site-opts.d/http.conf... php-1 | (init-webserver-config): Processing /etc/nginx/site-opts.d/https.conf.template → /etc/nginx/site-opts.d/https.conf... php-1 | â„šī¸ NOTICE (init-webserver-config): Enabling NGINX site with SSL "off"... php-1 | 👋 Hello, world! php-1 | [03-Nov-2025 19:35:29] NOTICE: fpm is running, pid 93 php-1 | [03-Nov-2025 19:35:29] NOTICE: ready to handle connections php-1 | HTTP Status Code: 200 php-1 | ✅ NGINX + PHP-FPM is running correctly. ``` :: You can see our `👋 Hello, world!` is executing *after* the initialization of `10-init-webserver-config.sh`. ## Running Custom Artisan Commands If you need to run a custom artisan command at start up, you could update your `entrypoint.d/99-my-script.sh` file to look like this: ```bash [entrypoint.d/99-my-script.sh] #!/bin/sh # Check if the artisan file exists if [ -f "$APP_BASE_DIR/artisan" ]; then # Run the custom artisan command php "$APP_BASE_DIR/artisan" my:custom-command else # If the artisan file is not found, stop the container echo "❌ Artisan file not found in $APP_BASE_DIR" exit 1 fi # Exit with a success code exit 0 ``` #### Best practices for running custom artisan commands - Use `/bin/sh` for the best compatibility between Alpine and Debian. If you choose to use `/bin/bash`, your script will only be able to run on Debian-based images. - Check to see if the artisan file exists before running the command - Exit with a success code - Name your file appropriately, so it executes in logic order with the other scripts (see our [default configurations](https://serversideup.net/open-source/docker-php/docs/getting-started/default-configurations#default-entrypoint-scripts)) You can change the name of your file to whatever you want, but we recommend using a number prefix to ensure it executes in the correct order. For example, if you want to run a command after the default scripts, you could name your file `99-my-command.sh`. If you want to run a command before the default scripts, you could name your file `1-my-command.sh`. ## Advanced Scenarios: S6 Overlay dependencies If you want to customize an image that uses S6 Overlay (`fpm-nginx` or `fpm-apache`), you may have an advanced scenario where you have a custom S6 service that needs to be executed after one of our entrypoint scripts. In order to do this, you'll need to move all our scripts from the `/etc/entrypoint.d` directory to the `/etc/s6-overlay/scripts` directory. This would be a very time consuming scenario if you did this manually, but thankfully you can use our `docker-php-serversideup-s6-init` script to do this for you. ```dockerfile [Dockerfile] FROM serversideup/php:8.5-fpm-nginx # Set the user to root for our build steps USER root # If you have your own one-shot scripts, copy them to the entrypoint.d directory COPY --chmod=755 ./entrypoint.d/ /etc/entrypoint.d/ # Copy our entrypoint scripts into the S6 Overlay scripts directory RUN docker-php-serversideup-s6-init # If you have your own long running services, copy them to the s6 directory COPY --chmod=755 ./my-s6-service/ /etc/s6-overlay/s6-rc.d/my-s6-service/ # Drop back to the non-root user USER www-data ``` In the above file, we're copying our "one-shot" scripts to the `/etc/entrypoint.d` directory and our long running services to the `/etc/s6-overlay` directory. One-shot scripts are scripts that are intended to run quickly and then move on. Long running services are services that are intended to run for a long time and need to be monitored and restarted if they crash. The magic happens when we run `docker-php-serversideup-s6-init`. This script will move all our scripts from the `/etc/entrypoint.d` directory to the `/etc/s6-overlay/scripts` directory and set the correct dependencies for our S6 services. You can now reference our script names as dependencies in your own S6 service. ::u-button --- ariaLabel: Learn more about S6 Overlay class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn more about S6 Overlay size: md target: _blank to: https://github.com/just-containers/s6-overlay trailing-icon: i-lucide-arrow-right variant: outline --- :: # Common Issues ## Overview This guide covers the most common issues users encounter and their solutions. If you don't find your issue here, check out our [Getting Help](https://serversideup.net/open-source/docker-php/docs/troubleshooting/getting-help) guide for community and professional support options. ## Permission Issues Permission problems are one of the most common issues when working with Docker containers. They typically manifest as "Permission denied" errors when trying to write files. ### Understanding the Problem By default, these images run as a non-root user (`www-data`) for security. When your host machine uses a different user ID (UID) or group ID (GID), file permission conflicts can occur. If your command is failing during build, then you likely need to switch to root to perform root tasks. ```dockerfile [Dockerfile] FROM serversideup/php:8.5-fpm-nginx USER root # Install system packages RUN install-php-extensions intl bcmath # Switch back to www-data USER www-data ``` ::warning Always switch back to a non-root user after completing privileged operations. Running containers as root is a security risk. :: If your container is failing to run during runtime, then you may have a more advanced permissions issue. See our guide on understanding file permissions. ::u-button --- ariaLabel: Understanding File Permissions class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Understanding File Permissions size: md to: https://serversideup.net/open-source/docker-php/docs/guide/understanding-file-permissions trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Port Already in Use ### Error Message ```text Error starting xyz service: listen tcp 0.0.0.0:80: bind: address already in use ``` ### Solution Another process is using the port. Find and stop it, or use a different port: **Find what's using the port:** ```bash # On Linux/macOS sudo lsof -i :80 # On Windows netstat -ano | findstr :80 ``` **If you must use a different port:** ```yaml [compose.yml] {4-6} services: php: image: serversideup/php:8.5-fpm-nginx ports: - "8080:8080" # Use port 8080 instead - "8443:8443" ``` Access your application at `http://localhost:8080`. ## Getting More Help If your issue isn't covered here: 1. **Search GitHub Discussions** - Someone may have encountered the same problem 2. **Check the Documentation** - Review guides specific to your setup 3. **Ask the Community** - Post in GitHub Discussions or Discord 4. **Review Container Logs** - Most issues show helpful error messages ::u-button --- ariaLabel: View All Support Options class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: View All Support Options size: md to: https://serversideup.net/open-source/docker-php/docs/troubleshooting/getting-help trailing-icon: i-lucide-arrow-right variant: outline --- :: ::tip When asking for help, always include your image version, `compose.yml`, relevant error messages, and what you've already tried. This helps others help you faster! :: # Getting Help ## You're Not Alone Getting stuck is a normal part of development. Whether you're troubleshooting a configuration issue, planning a complex deployment, or need help with a custom implementation, there are support options available to help you succeed. This guide will help you find the right resource to get unblocked quickly. ## Free Community Support ::note Want guaranteed response times from the maintainers? View our [Professional Support](https://serversideup.net/open-source/docker-php/#professional-support) options. :: Our community is active, helpful, and welcoming. Most questions can be answered through these free resources: ### GitHub Discussions **Best for:** General questions, feature requests, and community discussions Our GitHub Discussions is the most popular place to get help from both the maintainers and the community. It's searchable, so your question might help others in the future. ::u-button --- ariaLabel: Browse GitHub Discussions class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Browse GitHub Discussions size: md target: _blank to: https://github.com/serversideup/docker-php/discussions trailing-icon: i-lucide-arrow-right variant: outline --- :: ::tip Before posting, search existing discussions - your question might already be answered! :: ### Discord Community **Best for:** Quick questions, real-time chat, and community interaction Join over 3,000+ developers in our Discord server. It's a great place for quick questions, real-time troubleshooting, and connecting with other developers using serversideup/php. ::u-button --- ariaLabel: Join Our Discord class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Join Our Discord size: md target: _blank to: https://serversideup.net/discord trailing-icon: i-lucide-message-circle variant: outline --- :: ::note Discord is great for quick questions, but GitHub Discussions is better for complex issues that need detailed troubleshooting or permanent documentation. :: ### GitHub Issues **Best for:** Bug reports and specific technical issues with the images Found a bug or experiencing unexpected behavior? Open a GitHub issue with detailed reproduction steps. ::u-button --- ariaLabel: Report an Issue class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Report an Issue size: md target: _blank to: https://github.com/serversideup/docker-php/issues/new trailing-icon: i-lucide-bug variant: outline --- :: **When to use GitHub Issues:** - The Docker images aren't behaving as documented - You've found a security vulnerability - You have a specific, reproducible bug ::warning Please don't use GitHub Issues for general questions or support requests. Use GitHub Discussions or Discord instead. :: ## How to Ask for Help Effectively We've compile and entire guide on how to get answers quickly from the community. ::u-button --- ariaLabel: How to Ask for Help Effectively class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: How to Ask for Help Effectively size: md target: _blank to: https://serversideup.net/ask-for-help/ trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Professional Support Need guaranteed response times, direct access to the maintainers, or help with your specific implementation? We offer professional support options. ### One-Time Questions **Perfect for:** Specific technical questions or getting unstuck on a particular issue Schedule a focused session to get expert help with: - Docker-specific implementation questions - Complex deployment scenarios - Performance optimization guidance - Security configuration review - Custom implementation planning ::u-button --- ariaLabel: Schedule a Session class: font-bold text-white bg-blue-600 hover:bg-blue-700 color: primary label: Schedule a Session size: md target: _blank to: https://schedule.serversideup.net/team/serversideup/quick-chat-with-jay trailing-icon: i-lucide-calendar variant: solid --- :: **What you get:** - Direct access to serversideup/php maintainers - 1-hour focused troubleshooting or consulting - Screen sharing and hands-on help - Clear next steps and recommendations ### Ongoing Development & Support **Perfect for:** Teams needing continuous development support or complex projects Get dedicated access to senior engineers for ongoing development, deployment assistance, and technical support. ::u-button --- ariaLabel: Learn About Retainer Services class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn About Retainer Services size: md target: _blank to: https://serversideup.net/hire-us/ trailing-icon: i-lucide-arrow-right variant: outline --- :: **What's included:** - 🎨 **Full-Stack Development** - Frontend, backend, and everything in between - đŸ—ī¸ **Application Architecture** - Design scalable, maintainable solutions - đŸŽ¯ **UI/UX Design** - Beautiful, functional interfaces using Figma - đŸŗ **Docker & DevOps** - Container orchestration, CI/CD pipelines, and automation - đŸ–Ĩī¸ **Managed Hosting** - Deploy anywhere (self-hosted or cloud) - 📊 **Database Design** - Optimize for performance and scale - 🔒 **Security Hardening** - Production-ready security configurations - 📱 **Mobile App Development** - iOS & Android using modern frameworks **Key advantages:** - **Run Anywhere** - We specialize in both self-hosted infrastructure and cloud deployments (AWS, Google Cloud, DigitalOcean, etc.) - **No Vendor Lock-In** - Your infrastructure, your control - **Fixed Monthly Pricing** - Predictable costs, no hourly billing surprises - **Development-First** - 90%+ of time spent on actual development, not meetings - **Complete Stack** - One team handling design, development, deployment, and maintenance ::note **Why choose us?** We built serversideup/php and maintain it in production for our own clients. We know these images inside and out because we use them every day for real-world applications. :: ## We're Here to Help Whether you choose free community support or professional services, we're committed to your success with serversideup/php. Don't hesitate to reach out - we were all beginners once, and there's no such thing as a "stupid question." Happy coding! 🚀 # Environment Variable Specification ::lead-p Environment variables are a very powerful way to customize your container. Use the document below to see what options are available to customize without requiring you to mount any custom configuration files. :: Setting environment variables all depends on what method you're using to run your container, but for most cases you might be using Docker Compose. ::u-button --- ariaLabel: Learn how environment variables work with Docker Compose class: font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500 color: primary label: Learn environment variables work with Docker Compose size: md target: _blank to: https://docs.docker.com/compose/environment-variables/#set-environment-variables-in-containers trailing-icon: i-lucide-arrow-right variant: outline --- :: ## Available Environment Variables | **Variable Name** | **Description** | **Used in variation** | | :-----------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------: | | `APACHE_DOCUMENT_ROOT`:br*Default: "/var/www/html/public"* | Sets the directory from which Apache will serve files. ([Official docs](https://httpd.apache.org/docs/2.4/mod/core.html#documentroot)) | fpm-apache | | `APACHE_HTTP_PORT`:br*Default: "8080"* | Set the port for HTTP. ([Official docs](https://httpd.apache.org/docs/2.4/mod/core.html#port)) | fpm-apache | | `APACHE_HTTPS_PORT`:br*Default: "8443"* | Set the port for HTTPS. ([Official docs](https://httpd.apache.org/docs/2.4/mod/core.html#port)) | fpm-apache | | `APACHE_MAX_CONNECTIONS_PER_CHILD`:br*Default: "0"* | Sets the limit on the number of connections that an individual child server process will handle.([Official docs](https://httpd.apache.org/docs/2.4/mod/mpm_common.html#maxconnectionsperchild)) | fpm-apache | | `APACHE_MAX_REQUEST_WORKERS`:br*Default: "150"* | Sets the limit on the number of simultaneous requests that will be served. ([Official docs](https://httpd.apache.org/docs/2.4/mod/mpm_common.html#maxrequestworkers)) | fpm-apache | | `APACHE_MAX_SPARE_THREADS`:br*Default: "75"* | Maximum number of idle threads. ([Official docs](https://httpd.apache.org/docs/2.4/mod/mpm_common.html#maxsparethreads)) | fpm-apache | | `APACHE_MIN_SPARE_THREADS`:br*Default: "10"* | Minimum number of idle threads to handle request spikes. ([Official docs](https://httpd.apache.org/docs/2.4/mod/mpm_common.html#minsparethreads)) | fpm-apache | | `APACHE_RUN_GROUP`:br*Default: "www-data"* | Set the username of what Apache should run as. | fpm-apache | | `APACHE_RUN_USER`:br*Default: "www-data"* | Set the username of what Apache should run as. | fpm-apache | | `APACHE_START_SERVERS`:br*Default: "2"* | Sets the number of child server processes created on startup.([Official docs](https://httpd.apache.org/docs/2.4/mod/mpm_common.html#startservers)) | fpm-apache | | `APACHE_THREAD_LIMIT`:br*Default: "64"* | Set the maximum configured value for ThreadsPerChild for the lifetime of the Apache httpd process. ([Official docs](https://httpd.apache.org/docs/2.4/mod/mpm_common.html#threadlimit)) | fpm-apache | | `APACHE_THREADS_PER_CHILD`:br*Default: "25"* | This directive sets the number of threads created by each child process. ([Official docs](https://httpd.apache.org/docs/2.4/mod/mpm_common.html#threadsperchild)) | fpm-apache | | `APP_BASE_DIR`:br*Default: "/var/www/html"* | Change this only if you mount your application to a different directory within the container. â„šī¸ Be sure to change `NGINX_WEBROOT`, `APACHE_DOCUMENT_ROOT`, or `CADDY_SERVER_ROOT` if it applies to your use case as well. | all | | `AUTORUN_DEBUG`:br*Default: "false"* | Enable debug mode for the Laravel automations. | all | | `AUTORUN_ENABLED`:br*Default: "false"* | Enable or disable all automations. It's advised to set this to `false` in certain CI environments (especially during a composer install). If this is set to `false`, all `AUTORUN_*` behaviors will also be disabled. | all | | `AUTORUN_LARAVEL_OPTIMIZE`:br*Default: "true"* | Automatically run "php artisan optimize" on container, attempting to `--except` in Laravel > `v11.38.0` ([Official docs](https://laravel.com/docs/12.x/deployment#optimization)) :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_CONFIG_CACHE`:br*Default: "true"* | Automatically run "php artisan config\:cache" on container start. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_EVENT_CACHE`:br*Default: "true"* | Automatically run "php artisan event\:cache" on container start. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_MIGRATION`:br*Default: "true"* | Automatically run `php artisan migrate --force` on container start. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_MIGRATION_DATABASE`:br*Default: null* | Run migrations on a specific database. In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). If `null`, it will use the default database connection. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_MIGRATION_FORCE`:br*Default: "true"* | Force migrations to run in production without confirmation. Set to `false` to disable the `--force` flag. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_MIGRATION_ISOLATION`:br*Default: "false"* | Requires Laravel v9.38.0 or higher and a database that supports table locks. Automatically run `php artisan migrate --force --isolated` on container start. :br:br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. :br â„šī¸ Does not work with SQLite. :br â„šī¸ Only works with `AUTORUN_LARAVEL_MIGRATION_MODE = default`. | all | | `AUTORUN_LARAVEL_MIGRATION_MODE`:br*Default: "default"* | Set the migration mode. Valid options: `default` (runs `php artisan migrate`), `fresh` (runs `php artisan migrate:fresh`), or `refresh` (runs `php artisan migrate:refresh`). :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. :br âš ī¸ WARNING:`fresh` and `refresh` are destructive and will drop all tables. Only use these in development or testing environments. | all | | `AUTORUN_LARAVEL_MIGRATION_SEED`:br*Default: "false"* | Automatically seed the database after migrations by adding the `--seed` flag. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK`:br*Default: "false"* | Skip the database connection check before running migrations. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_MIGRATION_TIMEOUT`:br*Default: "30"* | The number of seconds to wait for the database to come online before attempting `php artisan migrate`.. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_ROUTE_CACHE`:br*Default: "true"* | Automatically run "php artisan route\:cache" on container start. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_STORAGE_LINK`:br*Default: "true"* | Automatically run "php artisan storage\:link" on container start. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `AUTORUN_LARAVEL_VIEW_CACHE`:br*Default: "true"* | Automatically run "php artisan view\:cache" on container start. :br â„šī¸ Requires `AUTORUN_ENABLED = true` to run. | all | | `CADDY_ADMIN`:br*Default: "off"* | Enable Caddy admin interface. ([Official docs](https://caddyserver.com/docs/caddyfile/options#admin)) | frankenphp | | `CADDY_AUTO_HTTPS`:br*Default: "off"* | Enable automatic HTTPS. ([Official docs](https://caddyserver.com/docs/caddyfile/options#auto-https)) | frankenphp | | `CADDY_GLOBAL_OPTIONS`:br*Default: ""* | Set global options for the Caddy server. ([Official docs](https://caddyserver.com/docs/caddyfile/options)) | frankenphp | | `CADDY_HTTP_PORT`:br*Default: "8080"* | Set the port for HTTP. ([Official docs](https://caddyserver.com/docs/caddyfile/options#http-port)) | frankenphp | | `CADDY_HTTP_SERVER_ADDRESS`:br*Default: "http\://"* | Set the server address for HTTP. ([Official docs](https://caddyserver.com/docs/caddyfile/concepts#addresses)) | frankenphp | | `CADDY_HTTPS_PORT`:br*Default: "8443"* | Set the port for HTTPS. ([Official docs](https://caddyserver.com/docs/caddyfile/options#https-port)) | frankenphp | | `CADDY_HTTPS_SERVER_ADDRESS`:br*Default: "https\://"* | Set the server address for HTTPS. ([Official docs](https://caddyserver.com/docs/caddyfile/concepts#addresses)) | frankenphp | | `CADDY_LOG_FORMAT`:br*Default: "console"* | Set the format for the Caddy log. ([Official docs](https://caddyserver.com/docs/caddyfile/directives/log#format-modules)) | frankenphp | | `CADDY_LOG_OUTPUT`:br*Default: "stdout"* | Set the output for the Caddy log. ([Official docs](https://caddyserver.com/docs/caddyfile/directives/log#output-modules)) | frankenphp | | `CADDY_PHP_SERVER_OPTIONS`:br*Default: ""* | Set PHP server options for the Caddy server. ([Official docs](https://frankenphp.dev/docs/config/#caddyfile-config)) | frankenphp | | `CADDY_SERVER_EXTRA_DIRECTIVES`:br*Default: ""* | Set extra directives for the Caddy server. ([Official docs](https://caddyserver.com/docs/caddyfile/directives)) | frankenphp | | `CADDY_SERVER_LOG_LEVEL`:br*Default: "warn"* | Set the server log level for the Caddy server. This can also be changed with `LOG_OUTPUT_LEVEL`, but `CADDY_SERVER_LOG_LEVEL` takes precedence. ([Official docs](https://caddyserver.com/docs/caddyfile/directives/log#format-modules)) | frankenphp | | `CADDY_SERVER_ROOT`:br*Default: "/var/www/html/public"* | Set the root directory for the Caddy server. ([Official docs](https://caddyserver.com/docs/caddyfile/directives/root)) | frankenphp | | `COMPOSER_ALLOW_SUPERUSER`:br*Default: "1"* | Disable warning about running as super-user | all | | `COMPOSER_HOME`:br*Default: "/composer"* | The COMPOSER\_HOME variable allows you to change the Composer home directory. This is a hidden, global (per-user on the machine) directory that is shared between all projects. | all | | `COMPOSER_MAX_PARALLEL_HTTP`:br*Default: "24"* | Set to an integer to configure how many files can be downloaded in parallel. Composer ships with 12 by default and must be between 1 and 50. If your proxy has issues with concurrency maybe you want to lower this. Increasing it should generally not result in performance gains. | all | | `DISABLE_DEFAULT_CONFIG`:br*Default: "false"* | Get full customization of the image and disable all default configurations and automations. | all | | `FRANKENPHP_CONFIG`:br*Default: ""* | Set the configuration for FrankenPHP. ([Official docs](https://frankenphp.dev/docs/config/#caddyfile-config)) | frankenphp | | `HEALTHCHECK_PATH`:br*Default: "/healthcheck"* | Set the path for the health check endpoint. ([Official docs](https://docs.docker.com/engine/reference/builder/#healthcheck)) | all (except `cli` and `frankenphp`) | | `HEALTHCHECK_SSL_CERTIFICATE_FILE`:br*Default: "/etc/ssl/healthcheck/localhost.crt"* | Set the path to the SSL certificate for the health check endpoint. | fpm-apache, fpm-nginx, frankenphp | | `HEALTHCHECK_SSL_PRIVATE_KEY_FILE`:br*Default: "/etc/ssl/healthcheck/localhost.key"* | Set the path to the SSL private key for the health check endpoint. | fpm-apache, fpm-nginx, frankenphp | | `LOG_OUTPUT_LEVEL`:br*Default:* :br *"warn" (for all)* :br *"info" (for frankenphp)* | Set your container output different verbosity levels: debug, warn, info, off | all | | `NGINX_ACCESS_LOG`:br*Default: "/dev/stdout"* | Set the default output stream for access log. | fpm-nginx | | `NGINX_ERROR_LOG`:br*Default: "/dev/stderr"* | Set the default output stream for error log. | fpm-nginx | | `NGINX_FASTCGI_BUFFERS`:br*Default: "8 8k"* | Sets the number and size of the buffers used for reading a response from a FastCGI server. ([Official Docs](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_buffers)) | fpm-nginx | | `NGINX_FASTCGI_BUFFER_SIZE`:br*Default: "8k"* | Sets the size of the buffer used for reading a response from a FastCGI server. ([Official Docs](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_buffer_size)) | fpm-nginx | | `NGINX_LISTEN_IP_PROTOCOL`:br*Default: "all"* | Set the IP protocol for NGINX to listen on. Valid values are "all", "ipv4", and "ipv6". ([Official Docs](https://nginx.org/en/docs/http/ngx_http_core_module.html#listen)) | fpm-nginx | | `NGINX_SERVER_TOKENS`:br*Default: "off"* | Display NGINX version in responses. ([Official Docs](https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens)) | fpm-nginx | | `NGINX_WEBROOT`:br*Default: "\`/var/www/html/public"* | Sets the root directory for requests. ([Official Docs](https://nginx.org/en/docs/http/ngx_http_core_module.html#root)) | fpm-nginx | | `NGINX_CLIENT_MAX_BODY_SIZE`:br*Default: "100M"* | Sets the max body size for requests. ([Official Docs](https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size)) | fpm-nginx | | `PHP_DATE_TIMEZONE`:br*Default: "UTC"* | Control your timezone. ([Official Docs](https://www.php.net/manual/en/datetime.configuration.php#ini.date.timezone)) | all | | `PHP_DISPLAY_ERRORS`:br*Default: Off* | Show PHP errors on screen. ([Official docs](https://www.php.net/manual/en/errorfunc.configuration.php#ini.display-errors)) | all | | `PHP_DISPLAY_STARTUP_ERRORS`:br*Default: Off* | Even when display\_errors is on, errors that occur during PHP's startup sequence are not displayed. ([Official docs](https://www.php.net/manual/en/errorfunc.configuration.php#ini.display-startup-errors)) | all | | `PHP_ERROR_LOG`:br*Default: "/dev/stderr"* | Name of the file where script errors should be logged. . ([Official docs](https://www.php.net/manual/en/errorfunc.configuration.php#ini.error-log)) | all | | `PHP_ERROR_REPORTING`:br*Default: "22527"* | Set PHP error reporting level. Must be a number. [Use this tool for help.](https://maximivanov.github.io/php-error-reporting-calculator/) ([Official docs](https://www.php.net/manual/en/errorfunc.configuration.php#ini.error-reporting)) | all | | `PHP_FPM_CHILD_PROCESS_USER`:br*Default: "www-data"* | âš ī¸ Only used if container is running as root. Set the user of the PHP-FPM child processes. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_FPM_CHILD_PROCESS_GROUP`:br*Default: "www-data"* | âš ī¸ Only used if container is running as root. Set the group of the PHP-FPM child processes. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_FPM_PM_CONTROL`:br*Defaults: :br fpm: dynamic :br fpm-apache: ondemand :br fpm-nginx: ondemand* | Choose how the process manager will control the number of child processes. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_FPM_PM_MAX_CHILDREN`:br*Default: "20"* | The number of child processes to be created when pm is set to static and the maximum number of child processes to be created when pm is set to dynamic. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_FPM_PM_MAX_REQUESTS`:br*Default: "0"* | The number of requests each child process should execute before respawning. This can be useful to work around memory leaks in 3rd party libraries. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_FPM_PM_MAX_SPARE_SERVERS`:br*Default: "3"* | The desired maximum number of idle server processes. Used only when pm is set to dynamic. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_FPM_PM_MIN_SPARE_SERVERS`:br*Default: "1"* | The desired minimum number of idle server processes. Used only when pm is set to dynamic. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_FPM_PM_START_SERVERS`:br*Default: "2"* | The number of child processes created on startup. Used only when pm is set to dynamic. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_FPM_PM_STATUS_PATH`:br*Default: ""* | The path to the PHP-FPM status page. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_FPM_POOL_NAME`:br*Default: "www"* | Set the name of your PHP-FPM pool (helpful when running multiple sites on a single server). | fpm\* | | `PHP_FPM_PROCESS_CONTROL_TIMEOUT`:br*Default: "10s"* | Set the timeout for the process control commands. ([Official docs](https://www.php.net/manual/en/install.fpm.configuration.php)) | fpm\* | | `PHP_MAX_EXECUTION_TIME`:br*Default: "99"* | Set the maximum time in seconds a script is allowed to run before it is terminated by the parser. ([Official docs](https://www.php.net/manual/en/info.configuration.php#ini.max-execution-time)) | all | | `PHP_MAX_INPUT_TIME`:br*Default: "-1"* | This sets the maximum time in seconds a script is allowed to parse input data, like POST and GET. Timing begins at the moment PHP is invoked at the server and ends when execution begins. The default setting is -1, which means that max\_execution\_time is used instead. Set to 0 to allow unlimited time. This directive is hardcoded to -1 for the CLI SAPI by PHP. ([Official docs](https://php.net/max-input-time)) | all | | `PHP_MAX_INPUT_VARS`:br*Default: "1000"* | Set the limits for number of input variables (e.g., POST, GET, or COOKIE variables) that PHP will process in a single request. ([Official docs](https://www.php.net/manual/en/info.configuration.php#ini.max-input-vars)) | all | | `PHP_MEMORY_LIMIT`:br*Default: "256M"* | Set the maximum amount of memory in bytes that a script is allowed to allocate. ([Official docs](https://www.php.net/manual/en/ini.core.php#ini.memory-limit)) | all | | `PHP_OPCACHE_ENABLE`:br*Default: "0" (to keep developers sane)* | Enable or disable OPcache. âš ī¸ This will set **both values** for `opcache.enable` and `opcache.enable_cli`. ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.enable)) | all | | `PHP_OPCACHE_ENABLE_FILE_OVERRIDE`:br*Default: "0"* | Enable or disable file existence override (file\_exists, etc.). ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.enable-file-override)) | all | | `PHP_OPCACHE_FORCE_RESTART_TIMEOUT`:br*Default: "180"* | The number of seconds to wait for a scheduled restart to begin if the cache isn't active, in seconds. If the timeout is hit, then OPcache assumes that something is wrong and will kill the processes holding locks on the cache to permit a restart. ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.force-restart-timeout)) | all | | `PHP_OPCACHE_INTERNED_STRINGS_BUFFER`:br*Default: "8"* | The amount of memory used to store interned strings, in megabytes. ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.interned-strings-buffer)) | all | | `PHP_OPCACHE_JIT`:br*Default: "off"* | Enable or disable the JIT compiler. ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit)) | all | | `PHP_OPCACHE_JIT_BUFFER_SIZE`:br*Default: "0"* | The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT. ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit-buffer-size)) | all | | `PHP_OPCACHE_MAX_ACCELERATED_FILES`:br*Default: "10000"* | The maximum number of keys (scripts) in the OPcache hash table. ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.max-accelerated-files)) | all | | `PHP_OPCACHE_MEMORY_CONSUMPTION`:br*Default: "128"* | The amount of memory used by the OPcache engine, in megabytes. ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.memory-consumption)) | all | | `PHP_OPCACHE_REVALIDATE_FREQ`:br*Default: "2"* | How often the OPcache checks for updates to cached files (in seconds). ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.revalidate-freq)) | all | | `PHP_OPCACHE_SAVE_COMMENTS`:br*Default: "1"* | Remove comments from OPcache to minify a bit further. Note: any code that depends on PHPDoc annotations can break from this. ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments)) | all | | `PHP_OPCACHE_VALIDATE_TIMESTAMPS`:br*Default: "1"* | Whether OPcache checks for changes to files, or requires reload of PHP to revalidate OPcache. ([Official docs](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.validate-timestamps)) | all | | `PHP_OPEN_BASEDIR`:br*Default: "None"* | Limit the files that can be accessed by PHP to the specified directory-tree, including the file itself. `open_basedir` is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed. ([Official docs](https://www.php.net/manual/en/ini.core.php#ini.open-basedir)) | all | | `PHP_POST_MAX_SIZE`:br*Default: "100M"* | Sets max size of post data allowed. ([Official docs](https://www.php.net/manual/en/ini.core.php#ini.post-max-size)) | all | | `PHP_REALPATH_CACHE_TTL`:br*Default: "120"* | The duration of time, in seconds for which to cache realpath information for a given file or directory. ([Official docs](https://www.php.net/manual/en/ini.core.php#ini.realpath-cache-ttl)) | all | | `PHP_SESSION_COOKIE_SECURE`:br*Default: 1 (true)* | Specifies whether cookies should only be sent over secure connections. ([Official docs](https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-secure)) | all | | `PHP_UPLOAD_MAX_FILE_SIZE`:br*Default: "100M"* | The maximum size of an uploaded file. ([Official docs](https://www.php.net/manual/en/ini.core.php#ini.upload-max-filesize)) | all | | `PHP_ZEND_DETECT_UNICODE`:br*Default: ""* | Check for BOM (Byte Order Mark) and see if the file contains valid multibyte characters. This detection is performed before processing of \_\_halt\_compiler(). Available only in Zend Multibyte mode. ([Official docs](https://www.php.net/manual/en/ini.core.php#ini.zend.detect-unicode)) | all | | `PHP_ZEND_MULTIBYTE`:br*Default: "Off"* | Enable or disable Zend Multibyte. ([Official docs](https://www.php.net/manual/en/ini.core.php#ini.zend.multibyte)) | all | | `S6_BEHAVIOUR_IF_STAGE2_FAILS`:br*Default: "2" (stop the container)* | Determines what the container should do if one of the service scripts fails ([Official docs](https://github.com/just-containers/s6-overlay#customizing-s6-overlay-behaviour)) | fpm-nginx, :br fpm-apache | | `S6_CMD_WAIT_FOR_SERVICES_MAXTIME`:br*Default: "0"* | The maximum time (in milliseconds) the services could take to bring up before proceeding to CMD executing ([Official docs](https://github.com/just-containers/s6-overlay#customizing-s6-overlay-behaviour)) | fpm-nginx, :br fpm-apache | | `S6_VERBOSITY`:br*Default: "1"* | Set the verbosity of "S6 Overlay" (the init system these images are based on). The default is "1" (print warnings and errors). The scale goes from 1 to 5, but the output will quickly become very noisy. If you're having issues, start here. You can also customize many other variables. ([Official docs](https://github.com/just-containers/s6-overlay#customizing-s6-behaviour)) | fpm-nginx, :br fpm-apache | | `SHOW_WELCOME_MESSAGE`:br*Default: "true"* | Show a helpful welcome message showing container information when the container starts. | all | | `SSL_CERTIFICATE_FILE`:br*Default: "/etc/ssl/private/self-signed-web.crt"* | Path to public certificate file for HTTPS. You must provide this file otherwise a self-signed key pair will be generated for you. | fpm-nginx, :br fpm-apache | | `SSL_MODE`:br*Default: "off"* | Configure how you would like to handle SSL. This can be "off" (HTTP only), "mixed" (HTTP + HTTPS), or "full" (HTTPS only). If you use HTTP, you may need to also change `PHP_SESSION_COOKIE_SECURE`. | fpm-nginx, :br fpm-apache, :br frankenphp | | `SSL_PRIVATE_KEY_FILE`:br*Default: "/etc/ssl/private/self-signed-web.key"* | Path to private key file for HTTPS. You must provide this file otherwise a self-signed key pair will be generated for you. | fpm-nginx, :br fpm-apache, :br frankenphp | # Command Reference ::lead-p We included a few commands in the `/usr/local/bin` directory to help you with your development experience. These commands are available in the `serversideup/php` image. :: ::note Since our images are unprivileged by default, you'll need to switch to the `root` user to run these commands. This is a security feature that ensures that your container is as secure as possible. :: ## Official PHP Docker Commands Since our images are based off the official PHP Docker images, any commands from the Official PHP Docker images are available in our images. You can find the full list of commands on the [official PHP Docker images documentation](https://hub.docker.com/_/php){rel="nofollow"}. ## docker-php-serversideup-dep-install-alpine This command will detect the operating system and install packages if it is an Alpine based system. This is helpful if you're building multiple operating systems from the same Dockerfile. ```bash [Example - Installing the git package on Alpine] # Usage: docker-php-serversideup-dep-install-alpine [alpine-packages] docker-php-serversideup-dep-install-alpine git ``` ## docker-php-serversideup-dep-install-debian This command will detect the operating system and install packages if it is a Debian based system. This is helpful if you're building multiple operating systems from the same Dockerfile. ```bash [Example - Installing the git package on Debian] # Usage: docker-php-serversideup-dep-install-debian [debian-packages] docker-php-serversideup-dep-install-debian git ``` ## docker-php-serversideup-entrypoint Our default entrypoint script that is used for all images. ## docker-php-serversideup-install-php-ext-installer This is an internal helper script to shorten up the syntax for the installation of the PHP extension installer. ```bash [Example - Installing the PHP extension installer] # Usage: docker-php-serversideup-install-php-ext-installer [version] docker-php-serversideup-install-php-ext-installer 2.2.0 ``` ## docker-php-serversideup-set-file-permissions This command is used to set the file permissions of a service in the container. This is useful for development environments where you want to match the user and group ID of the host machine. [Learn more about working with file permissions →](https://serversideup.net/open-source/docker-php/docs/guide/understanding-file-permissions) ```bash [Example - Setting the file permissions of the NGINX service] # Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP docker-php-serversideup-set-file-permissions --owner 1000:1000 ``` The above command will automatically detect the service and update the file permissions accordingly. ## docker-php-serversideup-set-id This command is used to set the user and group ID of the `www-data` user in the container. This is useful for development environments where you want to match the user and group ID of the host machine. [Learn more about working with file permissions →](https://serversideup.net/open-source/docker-php/docs/guide/understanding-file-permissions) ```bash [Example - Setting the user and group ID of the www-data user] # docker-php-serversideup-set-id [username] [uid]:[gid] docker-php-serversideup-set-id www-data 1000:1000 ``` ## docker-php-serversideup-s6-init This command is used to copy our entrypoint scripts into the S6 Overlay scripts directory. This is useful if you're using S6 Overlay and want to ensure your scripts are executed in the correct order. [Learn more about using S6 Overlay dependencies →](https://serversideup.net/open-source/docker-php/docs/customizing-the-image/adding-your-own-start-up-scripts#advanced-scenarios-s6-overlay-dependencies) ## docker-php-serversideup-s6-install This is a command used at build time to install a specific version of S6 Overlay. ## install-php-extensions This command is a wrapper around the `docker-php-ext-install` command that comes with the official PHP Docker images. This command allows you to install PHP extensions with a single command. [Check out the docker-php-extension-installer project on GitHub →](https://github.com/mlocati/docker-php-extension-installer){rel="nofollow"} Here's an example of how you can use this command: ```bash [Example - Installing the "intl" extension] install-php-extensions intl ```