Scripts de CLI: ¿dentro o fuera de Docker?

Tenés una aplicación web montada sobre Docker.

Cuando accedés usando el navegador todo funciona a las mil maravillas.

Está todo listo para ir a producción.

O casi.

Existen algunas pequeñas tareas que hay que hacer por fuera de la web. Limpiar archivos viejos… borrar las cuentas de usuario inactivas… lo típico, bah.

Qué mejor para esto que un script de CLI, ¿no?

Así que, ahí fuiste a codearlo.

Y ahora, hay que probarlo:

php my_script.php

No funciona.

¿Qué pasó?

La conexión a la base de datos falla.

¿Cómo es posible?

A ver qué dice el archivo .env:

DB_HOST="db"
DB_NAME="my_db"
DB_USER="my_user"
DB_PASS="my_pass"

Nada extraño por aquí.

El archivo de conexión:

<?php

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$conn = new PDO("mysql:dbname={$_ENV['DB_NAME']};host={$_ENV['DB_HOST'}", $_ENV['DB_USER'], $_ENV['DB_PASS'] );

Por aquí tampoco hay nada raro…

¡Momento!

Si la URL a la que ingresás es http://localhost:8080… el host de la db ¿no debería ser localhost?

Probar no cuesta mucho… cambiás db por localhost y… ¡funciona!

Listo, vamos a producción.

mmm, mejor hacemos una última prueba del sitio, ¿no?

Boom. Error 500.

PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000] [2002] No such file or directory in...

¿Cómo es posible? Si recién funcionaba…

¿Es que acaso es imposible hacer funcionar la web y el script de CLI sobre Docker?

No, claro que no.

Veámoslo paso a paso.

Qué pasa cuando se accede vía web

Cuando accedés a tu aplicación a través de la url http://localhost:8080, a pesar de que diga localhost, no es realmente tu computadora la que atiende esa petición (Bueno técnicamente sí lo es, pero a través de Docker).

El :8080 juega un papel muy importante.

Para que esto funcione, el puerto 8080 debe estar mapeado al puerto 80 del webserver que está corriendo en tu contenedor Docker.

Esto significa que, si bien tu computadora está escuchando a través del puerto 8080, lo que hace es re-enviar todo el tráfico recibido a través de él hacia el puerto 80 dentro del contenedor Docker configurado a tal efecto (Probablemente esto está definido en el archivo docker-compose.yml).

Distinto sería el caso si estuvieses usando el servidor incorporado a php (Es decir, si iniciaras tu aplicación vía php -S localhost:8080). En tal caso, la web y el CLI estarían usando el mismo entorno y, por lo tanto, no tendrías problemas.

Veamos ahora qué es lo que ocurre en el otro caso.

Qué pasa cuando se accede vía CLI

Lo primero que debés comprender es qué es exactamente lo que se ejecuta cuando hacés php my_script.php.

Ante todo, estás invocando al intérprete de php pasándole como argumento la cadena my_script.php.

Hasta aquí supongo que no hay nada muy novedoso, ¿cierto?

El problema comienza cuando tu script depende de configuraciones de entorno, como en este ejemplo.

Lo que ocurre es que, precisamente, el entorno de tu host es diferente del de los contenedores Docker. De eso se tratan los contenedores: de unidades de ejecución aisladas del host.

De hecho, Docker tiene su propio manejo de redes interno.

Esto quiere decir que el nombre db dentro del contenedor está asociado con una IP, mientras que fuera del entorno Docker no.

Cómo solucionar el problema

Ahora que tenés claro por dónde pasa el problema, la solución es ejectuar el script de PHP dentro del contenedor.

Tenés varias formas de hacerlo. Te comento rápidamente dos de ellas:

Con docker-compose

Si estás usando docker-compose podés ejecutar el siguiente comando:

docker-compose exec my_service php my_script.php

Suponiendo que el servicio donde está tu script está activo y se llama my_service, lo que verás en pantalla será el resultado de la ejecución de tu script.

Sin docker-compose

Si no usás docker-compose podés usar un comando del estilo de:

docker exec -v $(pwd):/var/www my_container php my_script.php

Este ejemplo es muy similar al de arriba. La principal diferencia es que en lugar de referirte a un servicio en ejecución, tenés que referirte directamente a un contenedor (my_container en este ejemplo) y tenés que montar los volúmenes en forma explícita.

Si el contenedor está corriendo el resultado que obtendrás será el mismo que el anterior.

Un pequeño consejo

Algo que puede ayudarte a evitar este tipo de situaciones es eliminar el php de tu host.

De esta forma, no vas a tener más opción que ejecutarlo dentro de Docker cuando así lo requieras.

Es cierto, es una opción algo extremista pero aún así puede resultarte útil ya que de esta forma siempre estarás seguro de que la versión de php que está utilizando tu script es exactamente la que esperás.

mchojrin

Por mchojrin

Ayudo a desarrolladores PHP a afinar sus habilidades técnicas y avanzar en sus carreras

¿Te quedó alguna duda? Publica aca tu pregunta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.