Blog
← Back to all blogs

How i ship my php apps to production using docker php-fpm and nginx

By Jim van Duijsen

July 20, 2025

phpdockernginxproduction

My PHP Setup for Production

Here's how I run a PHP application in production using Docker Compose to build the images, NGINX and PHP-FPM. This works perfectly for platforms that allow you to deploy apps using Docker Compose like Coolify, and you can also easily roll back because the code is baked into the image.

You can find the config on my github or copy below

Overview

This configuration sets up a classic PHP web application environment using two services:

  1. nginx: A web server that handles incoming HTTP requests
  2. app: A PHP-FPM service that executes the PHP code

The NGINX service is responsible for serving static files (like images, CSS) directly and forwarding requests for PHP files to the app service for processing.

Service-Specific Details

App Service (PHP)

Base Image: It starts with the official php:8.2.0-fpm image, which provides a foundation with PHP and the FastCGI Process Manager (FPM).

System Dependencies: It installs necessary libraries (zlib1g-dev, libzip-dev, unzip) for handling zip files.

PHP Extensions: It installs the zip PHP extension, which is needed by the project's dependencies.

Composer: It copies the Composer executable from the official Composer Docker image and then runs composer self-update to ensure it's the latest version.

Application Code:

  1. It sets the working directory to /var/www
  2. It copies the composer.json and composer.lock files and runs composer install to install the PHP dependencies defined in composer.json
  3. Finally, it copies the rest of the application code (your PHP files, etc.) into the container

NGINX Service

Base Image: It uses the lightweight nginx:1.24-alpine image.

Configuration: It copies your custom nginx.conf file into the container, overwriting the default NGINX configuration. This file defines how NGINX handles requests, including how it forwards PHP requests to the app service.

Static Files: It copies the public directory from your project into the container at /var/www/public. This allows NGINX to directly serve any static assets (images, CSS, etc.) found in that directory.

How They Work Together

Even though the ports section in docker-compose.yml is commented out, the NGINX service is designed to listen for incoming HTTP requests on port 80 (the default). When a request comes in, NGINX will:

  • Static files: If the request is for a static file (e.g., /css/index.css), it will serve it directly from the /var/www/public directory inside the NGINX container
  • PHP files: If the request is for a PHP file (e.g., /index.php), it will forward the request to the app service using the FastCGI protocol. The app service will execute the PHP script and send the response back to NGINX, which then sends it to the user

You can find the config on my github or copy below

Docker compose

services:  
  app:  
    build:  
      context: ./  
      dockerfile: Dockerfile-php  
    container\_name: php-fpm  
    restart: always  
    working\_dir: /var/www/

  nginx:  
    build:  
      context: ./  
      dockerfile: Dockerfile-nginx  
    container\_name: nginx  
    restart: always  
    \# ports: \#only for development  
    \#   \- 8000:80

Dockerfile-php

FROM php:8.2.0-fpm

RUN apt-get update && apt-get install \-y \\  
    zlib1g-dev \\  
    libzip-dev \\  
    unzip

RUN rm \-rf /var/lib/apt/lists/\*

RUN docker-php-ext-install zip

COPY \--from=composer /usr/bin/composer /usr/bin/composer

RUN composer self-update

WORKDIR /var/www

COPY composer.json composer.lock ./

RUN composer install

COPY ./ ./

Dockerfile-nginx

FROM nginx:1.24-alpine

COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf  
COPY ./public /var/www/public

Ngnix.conf
Needs to go in the nginx folder

server {  
    listen 80;  
    index index.php;  
    error\_log  /var/log/nginx/error.log;  
    access\_log /var/log/nginx/access.log;  
    error\_page 404 /index.php;  
    root /var/www/public;

    location \~ \\.php$ {  
        try\_files $uri \=404;  
        fastcgi\_pass app:9000;  
        fastcgi\_index index.php;  
        include fastcgi\_params;  
        fastcgi\_param SCRIPT\_FILENAME $document\_root$fastcgi\_script\_name;  
    }

    location / {  
        try\_files $uri $uri/ /index.php?$query\_string;  
        gzip\_static on;  
    }  
}