Introducción
Seeeeeeeeeeeeguro que has tenido problemas instalando versiones de php, extensiones de php, configurando nginx o apache, instalado bases de datos o usando alguna en local…en fin! Que como ya a estas alturas sabrás, cuando se trata de desarrollar, lo que lo peta es poder levantar un entorno virtual con todas las versiones que tocan y todas las dependencias que tocan y olvidarse del resto. Por eso, vamos a intentar hacer un 💡 Docker Plataforma que permite crear y ejecutar contenedores: entornos aislados y ligeros donde corren tus aplicaciones sin ensuciar tu máquina. Más info → Skeleton para ello, porque Docker puede hacerte la vida mucho más fácil!

En este caso vamos a ver de intentar implementar un entorno en Docker que tenga:
- 💡 PHP 8.4 Lenguaje de programación muy usado para desarrollo web del lado del servidor. La versión 8.4 es la más reciente. Más info →
- 💡 Composer Gestor de dependencias para PHP. Permite instalar y gestionar paquetes externos mediante un fichero composer.json. Más info →
- 💡 Nginx Servidor web ligero y muy rápido. Ideal para servir ficheros estáticos y hacer de proxy inverso hacia PHP u otros servicios. Más info →
- 💡 MariaDB Motor de base de datos relacional, compatible con MySQL. Es la alternativa open source más popular a MySQL. Más info →
Y para ello vamos a usar Docker y 💡 Docker Compose Herramienta para definir y levantar entornos multi-contenedor con un solo fichero YAML. Ideal para no tener que arrancar cada servicio por separado. Más info → .
¿Qué son Docker y Docker Compose? ¿No son lo mismo? ¿En qué se diferencian? Pues Docker es la tecnología base de gestión de contenedores virtuales y Docker Compose es el orquestador.
Estructura de ficheros
Para comenzar a hacer nuestro skeleton vamos a definir la estructura de ficheros que vamos a tener.
php-docker-skeleton/
├── docker/
│ ├── nginx/
│ │ └── default.conf # Configuración del servidor Nginx
│ └── php/
│ └── Dockerfile # Imagen PHP 8.4-FPM con extensiones y Composer
├── src/
│ └── index.php # Punto de entrada de la aplicación
├── .gitignore # Exclusiones de ficheros para el repositorio Git
├── composer.json # Dependencias PHP del proyecto
└── docker-compose.yml # Orquestación de servicios Docker
En el directorio /docker/nginx vamos a tener la config de nginx y en el de /docker/php vamos a tener la imagen de php y las extensiones. En /src vamos a tener el código fuente que vamos a querer mostrar, es decir, vamos a poner aquí el punto de entrada. Si fuera por ejemplo una app Symfony, esto sería el símil a public/index.php
Imagen de php en Docker con Dockerfile
💡 Dockerfile Fichero de texto sin extensión con instrucciones paso a paso para construir una imagen de Docker. Es como la receta de tu contenedor. es un fichero de texto simple sin extensión que contiene una serie de instrucciones paso a paso para crear la imagen de Docker. Estas instrucciones son comandos, por ejemplo para descargar una imagen de Docker, crear directorios y ficheros, descargar por ejemplo Composer etc. Lo necesitamos en el proyecto porque a diferencia de nginx o MariaDB que se descargan tal cual y no necesitan modificaciones, php necesita Dockerfile para por ejemplo instalar las extensiones.
Nuestro fichero Dockerfile ahora pinta así:
FROM php:8.4-fpm
# Instalar extensiones comunes
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
&& docker-php-ext-install pdo pdo_mysql zip
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
En la primera línea que vemos FROM php:8.4-fpm le estamos definiendo una imagen base ya hecha con la versión php 8.4 con 💡 FPM FastCGI Process Manager. Es el módulo que permite a PHP procesar peticiones web que le llegan desde un servidor como Nginx o Apache. . FPM es FastCGI Process Manager que sirve para ejecutar aplicaciones web junto a algún servidor tipo apache o nginx. Con RUN.. apt patatas le estamos diciendo primero que actualice los repositorios e instalar dependencias necesarias del sistema y con docker-php-ext-install etc utilizamos un fichero nativo de la imagen para instalar extensiones de php. El resto como puedes ir deduciendo son comandos de sistema para ir creando el entorno perfecto. Copiar Composer, establecer el directorio de trabajo con WORKDIR etc.
Configuración de nginx
Seguimos con la configuración para el servidor nginx:
server {
listen 80;
index index.php index.html;
root /var/www/html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
En esta configuración, definimos que:
- Escucharemos por el puerto 80
- Serviremos los ficheros del directorio
/var/www/htmldel contenedor. - Mandaremos cualquier fichero con extensión php a ejecutarlo con 💡 FastCGI Protocolo que conecta el servidor web (Nginx) con el intérprete de PHP. Es el puente para que Nginx le pase las peticiones PHP a FPM. por el puerto 9000. Nginx es genial para servir ficheros estáticos, pero no tiene ni idea de procesar ficheros php, por eso es necesario PHP-FPM.
Fichero docker-compose.yaml
Aquí está todo el tinglado, vamos a verlo:
services:
# Servidor Web (Nginx)
nginx:
image: nginx:latest
container_name: php-skeleton-nginx
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
# PHP
php:
build: ./docker/php
container_name: php-skeleton-php84
volumes:
- ./src:/var/www/html
depends_on:
- mariadb
# Base de Datos
mariadb:
image: mariadb:11
container_name: php-skeleton-mariadb
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: app
MYSQL_USER: app
MYSQL_PASSWORD: app
ports:
- "3306:3306"
volumes:
- mariadb_data:/var/lib/mysql
volumes:
mariadb_data:
En este entorno hemos decidido montar un servidor web nginx con un intérprete de php y una base de datos mariadb. Por ejemplo vamos a comenzar viendo con detalle el primer tramo, el del servidor web:
Servidor nginx
nginx:
image: nginx:latest
container_name: php-skeleton-nginx
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
image: nginx:latest: Descarga y utiliza la versión oficial más reciente del servidor Nginx.container_name: php-skeleton-nginx: Asigna un nombre personalizado al contenedor para identificarlo rápidamente en tu terminal.ports: - "8080:80": Conecta el puerto8080de tu PC local al puerto80del contenedor (donde Nginx escucha). Permite acceder a la web enhttp://localhost:8080.volumes:: Sincroniza archivos entre tu ordenador y el contenedor en tiempo real:./src:/var/www/html: Tu carpeta de código local (./src) se convierte en la carpeta pública del servidor (/var/www/html)../docker/.../default.conf...: Inyecta tu archivo de configuración personalizado de Nginx directamente en el contenedor.
depends_on: - php: Establece el orden de inicio. Obliga a Nginx a esperar a que el contenedorphpesté encendido antes de arrancar.
Servidor php
php:
build: ./docker/php
container_name: php-skeleton-php84
volumes:
- ./src:/var/www/html
depends_on:
- mariadb
build: ./docker/php: En lugar de descargar una imagen prefabricada, le indica a Docker que construya la imagen desde cero utilizando elDockerfileubicado en la ruta./docker/php.container_name: php-skeleton-php84: Asigna este nombre específico al contenedor para que sea fácil de identificar y gestionar desde la terminal.volumes: - ./src:/var/www/html: Sincroniza tu carpeta de código local (./src) con el directorio de trabajo del contenedor en tiempo real. Esto permite que PHP ejecute tus scripts actualizados sin necesidad de reconstruir la imagen.depends_on: - mariadb: Establece el orden de inicio. Indica que el contenedor de la base de datos (mariadb) arranque antes que el de PHP para evitar errores de conexión en tu aplicación.
y finalmente la base de datos
Base de datos
mariadb:
image: mariadb:11
container_name: php-skeleton-mariadb
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: app
MYSQL_USER: app
MYSQL_PASSWORD: app
ports:
- "3306:3306"
volumes:
- mariadb_data:/var/lib/mysql
image: mariadb:11: Descarga y utiliza la versión 11 oficial de MariaDB (un motor de base de datos directamente compatible con MySQL).container_name: php-skeleton-mariadb: Asigna un nombre personalizado al contenedor para que sea fácil de identificar en tu entorno.environment:: Define las variables de configuración inicial que se ejecutan la primera vez que se crea el contenedor:MYSQL_ROOT_PASSWORD: root: Establece la contraseña del superadministrador.MYSQL_DATABASE: app: Crea automáticamente una base de datos vacía llamadaapp.MYSQL_USERyMYSQL_PASSWORD: Crea un usuario estándar llamadoapp(con contraseñaapp) y le da acceso a la base de datos recién creada.
ports: - "3306:3306": Conecta el puerto 3306 de tu PC local al puerto 3306 del contenedor. Esto es súper útil para conectarte a la base de datos usando programas visuales (como DBeaver, TablePlus o HeidiSQL) sin tener que entrar a la consola del contenedor.volumes: - mariadb_data:/var/lib/mysql: Vincula la carpeta interna del contenedor donde se guardan los datos (/var/lib/mysql) con un 💡 volumen persistente Espacio de almacenamiento gestionado por Docker que sobrevive aunque borres o reinicies el contenedor. Tus datos no se pierden. de Docker llamadomariadb_data. Esto garantiza que la información de tu base de datos no se borre si apagas, reinicias o destruyes el contenedor.
Test!
Ahora para probarlo todo bien vamos a hacer un fichero php con una prueba de conexión a base de datos y lo vamos a guardar en src/index.php.
Usaremos 💡 PDO PHP Data Objects. Es la forma estándar y segura de conectarte a bases de datos desde PHP. Funciona con MySQL, MariaDB, PostgreSQL y más.
Más info →
para la conexión.
<?php
function connectToDatabase(): PDO
{
$host = 'mariadb';
$port = 3306;
$db = 'app';
$user = 'app';
$pass ='app';
$charset = 'utf8mb4';
$dsn = "mysql:host={$host};port={$port};dbname={$db};charset={$charset}";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
return new PDO($dsn, $user, $pass, $options);
}
try {
$pdo = connectToDatabase();
echo "PHP funciona y estás conectado a la base de datos";
} catch (PDOException $e) {
echo "Fallo en la conexión a la base de datos: " . $e->getMessage();
}
También crearemos nuestro fichero composer.json.
{
"name": "ejemplo/php-docker-skeleton",
"require": {
"ext-pdo": "*"
}
}
Y para ejecutarlo dentro del contenedor haríamos lo siguiente:
docker compose run --rm php composer install
Con esto nos aseguramos de usar el entorno de php definido en docker, evitando ejecutar nada con versiones de php locales.
Levantar el contenedor
Para levantar el contenedor basta con:
docker compose up -d --build
Y luego ir al navegador y entrar a: http://localhost:8080
Aquí deberías ver el texto del mensaje que hemos configurado antes en el fichero index.php.
Y EA eso es todo, más adelante veremos cómo crear un docker para NEST JS.
Saluditos y nos beermos! 🍻