Skip to Content
Course content

146: Deploying PHP Applications with Docker

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I see this happen all the time when developers first move toward containerization: they treat a Docker container like a lightweight virtual machine that they "log into" and configure manually. They'll spin up a generic PHP image, bash into it, install a few extensions, and then wonder why their deployment fails the moment they try to scale to a second server or push to a staging environment.

"Docker is just a place to run my code" vs. "The image is the artifact"

The biggest misconception is thinking that the container is just a wrapper for your code. In that mindset, you're likely using volumes to mount your local directory into the container, even in production. While that's great for development because you see changes instantly, it's a nightmare for deployment. If your production server relies on a volume mount, you're not actually deploying a "version" of your app; you're deploying a dependency on a specific file structure existing on a specific disk.

The correct approach is to treat your Docker image as a compiled artifact. Everything the app needs—the PHP runtime, the specific extensions, the composer dependencies, and the source code itself—should be baked into the image. When you push my-app:v1.2.0 to a registry, that image should be able to run on any machine in the world without needing a single file copied over via FTP or SSH.

Handling PHP's quirks with docker-php-ext-install

You can't just run apt-get install php-gd inside the official PHP images. I've seen so many people waste hours on this. The official PHP images use a special set of scripts to ensure extensions are compiled correctly for the specific version of PHP running in the container.

Let's say we're deploying a "Client Portal" app that generates PDF invoices. We need gd for image processing and pdo_mysql for the database. Here is how I would actually write that Dockerfile to make it production-ready:

FROM php:8.2-fpm-alpine

# We need system dependencies first for the PHP extensions to compile
RUN apk add --no-cache \
    libpng-dev \
    libjpeg-turbo-dev \
    freetype-dev

# Use the built-in helper to install and enable PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd pdo_mysql

# Set the working directory
WORKDIR /var/www/html

# Copy the code into the image (the "Artifact" part)
COPY . .

# Install composer dependencies
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN composer install --no-dev --optimize-autoloader

# Fix permissions for the web server
RUN chown -R www-data:www-data /var/www/html/storage

Notice the COPY . . line. In development, you'd override this with a volume in your compose file, but for deployment, this is what ensures your code is locked into the image version.

Connecting PHP-FPM to a Web Server

Here is another point where people get tripped up: the php:fpm image doesn't actually "serve" web pages. It's a FastCGI Process Manager. It speaks a protocol that browsers don't understand. You need a web server like Nginx to sit in front of it, acting as a translator.

I prefer using a docker-compose.yml file to define this relationship. It keeps the networking simple. You don't need to worry about IP addresses; you just refer to the service by its name.

services:
  app:
    build: .
    restart: always
    environment:
      - DB_HOST=db

  web:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app

  db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: portal_db
      MYSQL_ROOT_PASSWORD: secret_password

The key here is the web service. It takes the incoming HTTP request and passes it to the app service via port 9000. If you try to run the PHP-FPM container alone and visit port 80, you'll get nothing. It's a common "why isn't it working?" moment that usually just takes a quick look at the architecture to solve.




📋 Practical Task

Containerizing the Invoice-PDF Generator

You have a PHP application that requires the bcmath extension for precise currency calculations and the zip extension for bundling invoices. The application source code is located in the current directory.

Your Task: Create a Dockerfile and a docker-compose.yml file that accomplishes the following:

  • Use php:8.2-fpm-alpine as the base image.
  • Install the system dependencies required for the zip extension (hint: libzip-dev).
  • Use docker-php-ext-install to install both bcmath and zip.
  • Copy the current directory into /var/www/html within the image.
  • Set up a docker-compose.yml that orchestrates two services: app (built from your Dockerfile) and web (using the nginx:alpine image), ensuring the web server can communicate with the PHP app.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.