Buenas prácticas para crear un Dockerfile listo para producción para aplicaciones PHP
James Walker
22 de agosto de 2023
0 minutos de lecturaDocker es una plataforma de contenedorización que agrupa tu código, sus dependencias y el entorno de ejecución en unidades independientes que funcionan de manera idéntica en distintos entornos.
Dockerizar una aplicación PHP simplifica su implementación al empaquetar el entorno de ejecución de PHP, un servidor web, tu código fuente y las dependencias de Composer en un contenedor. Empezar a usar Docker es fácil. Sin embargo, hay algunas dificultades que debes evitar antes de usarlo de forma segura en producción. Para prevenir problemas, es fundamental elegir una imagen base adecuada, seleccionar el entorno de ejecución de PHP correcto para tu carga de trabajo y reforzar la seguridad de la imagen. En este tutorial, aprenderás a escribir un Dockerfile confiable para aplicaciones PHP.
Cómo crear un Dockerfile listo para producción para PHP
Si estás leyendo esto, probablemente ya escribiste tu aplicación PHP y ahora quieres dockerizarla para implementarla. Estas son diez buenas prácticas para maximizar el rendimiento, la seguridad, la confiabilidad y la facilidad de desarrollo:
1. Usa una imagen base específica
La comunidad de Docker publica una imagen oficial de Docker para todas las versiones compatibles de PHP. Hay varias variantes de imagen disponibles, con distintas combinaciones de sistemas operativos base, versiones del lenguaje e integraciones con servidores web (como Apache o FastCGI Process Manager (FPM)).
Es importante seleccionar la imagen específica que mejor se adapte a tus requisitos. Evita usar etiquetas genéricas, como php:latest, porque podrían exponerte a cambios no deseados que rompan el código y a bibliotecas innecesarias que solo se requieren para un sistema operativo completo. Por ejemplo, la imagen php:apache incluye actualmente PHP 8.2, pero cambiará inmediatamente a PHP 9.0 cuando se lance esa versión. Si seleccionas php:8.2-apache, te aseguras de que tu imagen siempre ejecute PHP 8.2, aunque deje de ser la versión actual:
El mismo principio se aplica a cualquier otra imagen de la que dependas, como composer para instalar dependencias. Selecciona la etiqueta de imagen que coincida con la versión principal de Composer que usas en tu máquina: composer:2 en lugar de composer:latest.
2. Usa variables de entorno para la configuración
Los contenedores Docker están diseñados para no conservar estado. Lo mejor es configurar tu aplicación mediante variables de entorno que estableces al iniciar el contenedor. Esto es preferible a depender de un archivo de configuración, que requeriría configurar siempre almacenamiento persistente para el contenedor mediante un volumen de Docker.
Las variables de entorno se establecen con la marca -e al iniciar un contenedor con docker run:
Tu código PHP puede obtener el valor de la variable llamando a la función getenv():
Estas son variables de entorno de ejecución que se establecen en contenedores individuales. A veces, quizás quieras establecer una variable en tu Dockerfile para que se proporcione automáticamente a los contenedores. Para hacerlo, usa la instrucción ENV:
También puedes definir variables de entorno durante la compilación y hacer referencia a ellas en otras instrucciones del Dockerfile mediante la palabra clave ARG:
Para establecer el valor del argumento, usa la marca --build-arg al ejecutar docker build:
3. Evalúa PHP FPM frente a Nginx/Apache
FPM es una implementación alternativa de PHP FastCGI para integrar PHP con servidores web, incluidos Apache y Nginx. Una alternativa a FPM es usar el servidor web Apache con mod_php. En comparación con la alternativa de Apache, FPM ofrece una administración avanzada de procesos sin bloqueo, la posibilidad de crear distintos grupos de procesos de trabajo y un manejo de errores más eficaz. El uso de FPM suele proporcionar una mejora significativa del rendimiento en aplicaciones grandes.
La imagen base de Docker de PHP está disponible en variantes FPM y mod_php:
php:8.2-fpm: Esta imagen y las etiquetas similares exponen una conexión FPM.php:8.2-apache: Estas imágenes incluyen una instalación de Apache y usanmod_phppara procesar las solicitudes PHP.
Si usas Apache y estás conforme con mod_php, selecciona una de las imágenes base :apache. Puedes agregar tu código PHP al directorio /var/www/html y empezar a ejecutarlo de inmediato.
Para usar FPM, necesitarás un contenedor independiente que ejecute un servidor como Nginx. Debes configurarlo como un proxy inverso que reenvía las solicitudes PHP al puerto que expone el proceso FPM en tu contenedor PHP. El puerto predeterminado es 9000.
Usa FPM si quieres obtener el máximo rendimiento y no te preocupa administrar dos contenedores: uno de FPM y otro con un servidor web delante. Las imágenes basadas en Apache son un excelente punto de partida para comenzar rápidamente, sin configurar contenedores adicionales ni ajustar la configuración de los grupos de procesos de trabajo de FPM.
4. Desactiva los registros de depuración y la notificación de errores innecesarios en producción
Las aplicaciones de producción no deben divulgar públicamente información que pueda ser útil para un atacante. Desactiva la configuración display_errors y display_startup_errors de PHP para evitar que se muestren excepciones no controladas en tu sitio web.
Para cambiar estos valores, modifica el archivo php.ini dentro de tu contenedor.
Empieza por crear el archivo modificado:
Luego, ajusta tu Dockerfile para copiar php.ini en la ubicación correcta del sistema de archivos del contenedor:
Quizás debas modificar el nivel de notificación de errores de PHP para usarlo en producción. Los errores de bajo nivel, como los avisos y las advertencias de funciones obsoletas, pueden llenar rápidamente los registros con información que no permite tomar medidas. Puedes establecer este valor dinámicamente al inicio del script según una variable de entorno:
5. Ejecuta tu contenedor PHP como usuario no root
De forma predeterminada, Docker ejecuta los contenedores como root. El proceso que se ejecuta dentro del contenedor tiene los mismos privilegios que el usuario root en tu host. Si alguien vulnera el contenedor y escapa de su aislamiento, los procesos maliciosos pueden ejecutar comandos arbitrarios en tu host.
Ejecutar tu contenedor como un usuario dedicado que no sea root ayuda a mitigar estos riesgos. Usa la instrucción USER en tu Dockerfile para cambiar a otro usuario a partir de ese punto:
6. Configura verificaciones de estado para el contenedor PHP
Docker tiene un mecanismo de verificación de estado integrado que detecta cuándo fallan los contenedores. Puedes configurar Docker para que ejecute automáticamente un comando de verificación de estado cada treinta segundos. Si el comando termina con el código 0, se considera que el contenedor está en buen estado. En cambio, si termina con el código 1, significa que la aplicación falló.
Puedes configurar verificaciones de estado para tus aplicaciones mediante la instrucción HEALTHCHECK en tu Dockerfile. En un servicio PHP, puedes usar curl para enviar una solicitud de red a uno de los puntos de conexión de tu aplicación. Si curl termina con 0, significa que recibió una respuesta con un código de estado HTTP del rango 2xx (éxito):
El estado de tus contenedores se muestra en el resultado del comando docker ps:
Cuando falla una verificación de estado, el estado del contenedor aparecerá como unhealthy. Los orquestadores de contenedores, como Kubernetes, usan esta señal para reiniciar o reemplazar automáticamente los contenedores que fallan.
7. Limita la cantidad de puertos que usas
Evita exponer puertos innecesarios en tus contenedores. Solo debes exponer los puertos a los que necesitan acceder procesos externos. Son los puertos a los que se conectarán otros contenedores y usuarios públicos.
En las aplicaciones PHP, suele ser el puerto 80 cuando usas una imagen con un servidor web incluido, como php:8.2-apache. En su lugar, las imágenes basadas en FPM deben exponer el puerto 9000, para que se conecte un contenedor independiente con el servidor web. El puerto FPM no debe estar abierto al público.
Del mismo modo, presta atención a la información que expones mediante las variables de entorno. Elimina las variables que no uses para reducir el riesgo de divulgación. Herramientas como docker inspect y cualquier software malicioso dentro del contenedor siempre pueden acceder a la lista completa de variables que estableciste.
8. Actualiza las dependencias con regularidad
Debes actualizar con regularidad todos los componentes de tu stack para aplicar nuevas correcciones de errores y actualizaciones de seguridad. Esto incluye lo siguiente:
Actualizaciones del sistema operativo base de tu imagen de Docker.
Actualizaciones de la versión de PHP y de las extensiones que usas.
Paquetes de Composer que instalaste como dependencias en tu proyecto.
Esto es fundamental para protegerte de nuevas vulnerabilidades. Debes volver a crear tu imagen con docker build --pull cada vez que la compiles para obtener actualizaciones. Así se descargará la versión más reciente de tu imagen base, incluidos los paquetes actualizados del sistema operativo y las versiones de PHP.
También debes ejecutar composer update periódicamente en tu proyecto para obtener las correcciones más recientes para tus dependencias. Puedes usar herramientas como Snyk para encontrar dependencias desactualizadas o vulnerables y obtener recomendaciones para actualizarlas a una versión corregida. Después de aplicar las actualizaciones de paquetes con Composer, vuelve a crear tu imagen de Docker para que las implementaciones en producción usen las nuevas versiones.
9. Limita las capacidades del contenedor a las mínimas necesarias
Las capacidades del kernel de Linux restringen las acciones potencialmente sensibles que pueden realizar los procesos. Docker ya ejecuta los contenedores con un conjunto sin privilegios de capacidades, pero estas siguen siendo excesivas para las cargas de trabajo PHP habituales. Por ejemplo, la configuración predeterminada permite que los procesos del contenedor cambien los permisos de archivos, eliminen procesos y manipulen los ID de usuario.
Puedes agregar y quitar capacidades del contenedor con las marcas --cap-add y --cap-drop de docker run. Lo más seguro es quitar todas las capacidades con --cap-drop=all y luego volver a agregar las específicas que necesite tu aplicación.
En el siguiente ejemplo, el contenedor solo recibe la capacidad CHOWN. Puede cambiar la propiedad de archivos y carpetas, pero no podrá realizar ninguna otra acción privilegiada:
10. Usa compilaciones en varias etapas para separar la compilación del entorno de ejecución
Las compilaciones en varias etapas son un mecanismo que facilita la escritura y el mantenimiento de canalizaciones de compilación más complejas. Permiten incluir varias imágenes base en el proceso de compilación y, a la vez, mantener un resultado pequeño y preciso.
El patrón de varias etapas es ideal para los Dockerfiles de PHP porque, por lo general, querrás instalar las dependencias de Composer durante la compilación. Las imágenes oficiales de Docker de PHP no incluyen Composer; el proyecto publica sus propias imágenes. Con una compilación en varias etapas, puedes hacer referencia al binario de Composer directamente en tu Dockerfile:
La imagen composer:2 se descartará cuando finalice la compilación. Este enfoque mantiene una buena separación entre la compilación y el entorno de ejecución sin aumentar el tamaño de la imagen final.
Cómo identificar y corregir vulnerabilidades con Snyk
Usar Docker puede mejorar la seguridad al proporcionar cierto nivel de aislamiento entre tus aplicaciones PHP y sus entornos host. Sin embargo, las imágenes de contenedor suelen contener vulnerabilidades de seguridad debido a paquetes desactualizados del sistema operativo y dependencias de Composer:

Analizar tu imagen en busca de vulnerabilidades es un paso fundamental antes de implementarla en producción. Puedes usar Snyk para analizar tu imagen de Docker PHP, e identificar y corregir vulnerabilidades. La Snyk Vulnerability Database incluye registros de todos los sistemas operativos y dependencias populares, incluidos los paquetes de PHP publicados en Packagist.
Para empezar a identificar vulnerabilidades con Snyk, crea un archivo PHP sencillo y guárdalo como index.php:
A continuación, escribe un Dockerfile para tu aplicación:
Crea la imagen con Docker:
Luego, ve al sitio web de Snyk y haz clic en Comenzar gratis para crear tu cuenta. Sigue los pasos para configurarla e instala la CLI de Snyk. También necesitarás tener instalado Node.js y npm:
Luego, ejecuta el comando snyk auth. Se abrirá una nueva pestaña del navegador donde podrás iniciar sesión en Snyk y autenticar la CLI. Después, vuelve a la terminal.
Para analizar tu imagen, ejecuta el siguiente comando:
El análisis puede tardar unos momentos. Luego, los resultados aparecerán en la terminal. Verás una lista de todas las vulnerabilidades detectadas, incluido el paquete afectado, las versiones vulnerables y un enlace para obtener más información:

El resumen al final muestra la cantidad total de problemas encontrados y sugiere imágenes base alternativas con menos vulnerabilidades.
Cómo corregir las vulnerabilidades detectadas
Si se encuentran vulnerabilidades, evalúa cada una para determinar si debes tomar medidas. No todas necesariamente representarán un problema para tu caso de uso. Concéntrate primero en resolver los problemas críticos, altos y medios.
A menudo, la forma más eficaz de empezar es reconstruir tu imagen con una versión actualizada de la imagen base. Las imágenes populares de la comunidad, como php:8.2-fpm, se vuelven a publicar periódicamente con nuevas versiones que incluyen correcciones de vulnerabilidades. Después de reconstruir la imagen, repite el análisis para ver cómo afecta el resultado.
El informe podría enumerar muchas vulnerabilidades en componentes de bajo nivel del sistema operativo. Considera usar una imagen base más pequeña y minimalista para reducir la superficie de ataque general. Las variantes de Alpine Linux, como php:fpm-alpine, incluyen muchos menos paquetes, lo que suele significar menos vulnerabilidades.
Al momento de escribir este artículo, la imagen php:8.2-fpm tiene noventa y ocho vulnerabilidades detectadas, por ejemplo:

La variante php:8.2-fpm-alpine solo tiene diez porque incluye muchos menos paquetes del sistema operativo:

Es posible que algunas vulnerabilidades no se puedan resolver en tu proyecto. Por ejemplo, los problemas en paquetes del sistema operativo podrían no afectar tu carga de trabajo, o quizá no haya un parche disponible. Puedes descartar una vulnerabilidad de un informe de Snyk pasando su ID al comando snyk ignore. Puedes encontrar el ID en la parte final de la URL de la vulnerabilidad, como SNYK-ALPINE317-CURL-3320725 en https://security.snyk.io/vuln/SNYK-ALPINE317-CURL-3320725:
Por último, recuerda que la seguridad de los contenedores depende de varios componentes. Necesitas una imagen base segura, una versión actualizada y con soporte del entorno de ejecución de PHP, además de versiones con parches de tus dependencias, los componentes de la cadena de herramientas y el código de la aplicación. Analizar periódicamente tu imagen en busca de vulnerabilidades te permitirá conocer su nivel de seguridad.
Resumen
Después de leer este artículo, deberías tener una imagen de Docker lista para producción con la que ejecutar tu aplicación PHP. Esta guía abordó algunas prácticas recomendadas para escribir tu Dockerfile y usar las imágenes que compilas, como elegir una imagen base específica, usar compilaciones de varias etapas para instalar dependencias y analizar en busca de vulnerabilidades para asegurarte de que tus cargas de trabajo sean aptas para implementarse en producción.
Si quieres más recomendaciones sobre imágenes de Docker y PHP, consulta la documentación de Docker y los demás artículos disponibles en el blog de Snyk.
Snyk es una plataforma de seguridad diseñada para desarrolladores y para cualquier persona responsable de proteger el código. Puede analizar tus imágenes de Docker para detectar vulnerabilidades en tus dependencias, paquetes del sistema operativo y código PHP. Snyk también ofrece un complemento para IDE que realiza análisis estáticos para detectar vulnerabilidades en cuanto aparecen. Regístrate ahora para encontrar y corregir vulnerabilidades automáticamente en tus imágenes de contenedores PHP.
Seguridad de contenedores centrada en los desarrolladores
Snyk encuentra y corrige automáticamente vulnerabilidades en imágenes de contenedores y cargas de trabajo de Kubernetes.



