Docker con PHP: Dockerfile para php-fpm, Nginx y docker-compose con MySQL

Contenerizar una aplicación PHP con Docker garantiza que el entorno de desarrollo sea idéntico al de producción y elimina el clásico «en mi máquina funciona». Con php-fpm como motor de PHP, Nginx como servidor web y docker-compose para orquestar los servicios, tienes un stack moderno listo para escalar.

Estructura del proyecto

mi-app/
??? docker/
?   ??? nginx/
?   ?   ??? default.conf
?   ??? php/
?       ??? Dockerfile
??? src/
?   ??? index.php
??? docker-compose.yml
??? composer.json

Dockerfile para php:8.3-fpm

# docker/php/Dockerfile
FROM php:8.3-fpm

# Dependencias del sistema necesarias para las extensiones PHP
RUN apt-get update && apt-get install -y 
    libpng-dev 
    libjpeg-dev 
    libfreetype6-dev 
    libzip-dev 
    libonig-dev 
    libxml2-dev 
    git 
    unzip 
    && rm -rf /var/lib/apt/lists/*

# Extensiones PHP
RUN docker-php-ext-configure gd 
        --with-freetype 
        --with-jpeg 
    && docker-php-ext-install 
        pdo_mysql 
        mbstring 
        gd 
        zip 
        bcmath 
        opcache 
        xml

# Instalar Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

# Directorio de trabajo
WORKDIR /var/www/html

# Copiar ficheros del proyecto
COPY . .

# Instalar dependencias de Composer
RUN composer install --no-dev --optimize-autoloader

# Permisos
RUN chown -R www-data:www-data /var/www/html/storage 
    && chmod -R 775 /var/www/html/storage

EXPOSE 9000
CMD ["php-fpm"]

Configuración de Nginx como proxy

# docker/nginx/default.conf
server {
    listen 80;
    server_name _;
    root /var/www/html/public;
    index index.php;

    # Servir ficheros estáticos directamente
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Pasar PHP a php-fpm
    location ~ .php$ {
        fastcgi_pass   php:9000;   # nombre del servicio en docker-compose
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include        fastcgi_params;
        fastcgi_read_timeout 300;
    }

    # Negar acceso a ficheros ocultos
    location ~ /. {
        deny all;
    }
}

docker-compose.yml con MySQL 8

version: '3.9'

services:
  php:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
    volumes:
      - .:/var/www/html
    environment:
      APP_ENV:   ${APP_ENV:-development}
      DB_HOST:   db
      DB_PORT:   3306
      DB_NAME:   ${DB_NAME:-mi_app}
      DB_USER:   ${DB_USER:-app}
      DB_PASS:   ${DB_PASS:-secret}
    depends_on:
      db:
        condition: service_healthy
    networks:
      - app-net

  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - .:/var/www/html
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php
    networks:
      - app-net

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootsecret
      MYSQL_DATABASE:      ${DB_NAME:-mi_app}
      MYSQL_USER:          ${DB_USER:-app}
      MYSQL_PASSWORD:      ${DB_PASS:-secret}
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 5s
      retries: 10
    networks:
      - app-net

networks:
  app-net:

volumes:
  db-data:

Comandos habituales

# Construir y levantar en background
docker compose up -d --build

# Ver logs en tiempo real
docker compose logs -f php

# Ejecutar comandos en el contenedor PHP
docker compose exec php php artisan migrate
docker compose exec php composer install

# Abrir una shell en el contenedor
docker compose exec php bash

# Detener y eliminar contenedores
docker compose down

# Eliminar también los volúmenes (cuidado: borra la BD)
docker compose down -v

Dockerfile para desarrollo (hot-reload)

# docker/php/Dockerfile.dev
FROM php:8.3-fpm

# Instalar extensiones de desarrollo adicionales
RUN pecl install xdebug 
    && docker-php-ext-enable xdebug

COPY docker/php/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini

# xdebug.ini
# xdebug.mode=debug
# xdebug.start_with_request=yes
# xdebug.client_host=host.docker.internal
# xdebug.client_port=9003

Buenas prácticas

  • Usa imágenes alpine donde puedas (php:8.3-fpm-alpine) para reducir el tamaño de la imagen.
  • Separa el Dockerfile de desarrollo (con Xdebug, Composer) del de producción (sin herramientas de dev).
  • Usa healthcheck en la BD para que el contenedor PHP no arranque antes de que MySQL esté listo.
  • No copies el directorio vendor/ ni .env en la imagen: usa .dockerignore.
  • En producción, construye la imagen con composer install --no-dev --optimize-autoloader y copia solo los ficheros necesarios.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP