Introducción a Docker: Nginx + PHP

En la anterior publicación nos centramos en revisar las capacidades y funcionamiento de Docker, con el objetivo de resolver un problema habitual en la gestión de entornos de desarrollo. También vimos un ejemplo muy básico en el que pusimos a funcionar Nginx bajo Docker con solo un Dockerfile. En estos momentos, lo que nos pide el cuerpo es seguir avanzando, de manera que vamos a añadir un servicio más a nuestro ecosistema Docker: PHP.

Presentaciones

No hace falta que os diga que PHP tiene muchos detractores, y es probable que sea porque nunca le han dado un uso correcto. Otros argumentarán que ya tuvo su tirón y que hay ahora alternativas más “saludables”… Pues yo os digo que el ecosistema PHP está bastante bien de salud: Magento, Laravel, WordPress, Slack y así un largo etcétera. Es una herramienta más que tenemos a nuestra disposición y no debemos denostarla por cosas del pasado. PHP v7.4.6 es más que un digno competidor en la guerra de los lenguajes de programación y se merece nuestro respeto.

PHPeando con Docker

Para añadir PHP a nuestra aplicación, lo que vamos a hacer es crear un servicio aparte en el que correrá el intérprete de PHP, que será un contenedor distinto al de Nginx y estará dentro de la misma red de Docker.

Recordad que tenéis el código terminado y comandos aquí. La recomendación es que os clonéis el repo y ejecutéis los comandos en la carpeta correspondiente.

Ahora vamos a trabajar con Dockerfiles diferentes (uno por cada servicio), ya que necesitamos algo más de complejidad. Las imágenes a construir estarán basadas en amazonlinux (algo desactualizadas, pero nos valen), y tendrán lo mínimo indispensable para poder hacer lo que queremos: cuanto menos tengan, menos tardarán en cocinarse y menos recursos consumirán.

Dockerfile para Nginx:

FROM amazonlinux:1

RUN yum clean all && yum update -y && yum install -y \
sudo

RUN yum install -y \
nginx install

RUN mkdir -p /www/myapp
WORKDIR /www/myapp

COPY docker_assets/myapp.conf /etc/nginx/conf.d/
COPY docker_assets/nginx.conf /etc/nginx/

RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log

COPY . .

CMD ["nginx", "-g", "daemon off;"]

En lenguaje natural:

  • Actualiza el sistema e instala los paquetes necesarios.
  • Crea el directorio de trabajo y úsalo como base.
  • Copia los ficheros de configuración de Nginx necesarios.
  • Enlaza log de errores a Ningx para poder verlo en directo.
  • Copia contenido de la app al directorio de trabajo.
  • Arranca Nginx.

DockerfilePHP para PHP:

FROM amazonlinux:1

RUN yum clean all && yum update -y && yum install -y \\
sudo

RUN yum install -y \\
php56 \\
php-pear \\
php56-cli \\
php56-common \\
php56-fpm \\
php56-soap \\
php56-pecl-redis 

RUN sed -e 's/127.0.0.1:9000/9000/' \\
        -e '/allowed_clients/d' \\
        -e '/catch_workers_output/s/^;//' \\
        -e '/error_log/d' \\
        -i /etc/php-fpm.d/www.conf 

RUN mkdir -p /www/myapp
WORKDIR /www/myapp

COPY . .

CMD /usr/sbin/php-fpm --nodaemonize

En lenguaje natural:

  • Actualiza el sistema e instala los paquetes necesarios.
  • Realiza una configuración extra para poder arrancar correctamente php-fpm en el contenedor.
  • Crea el directorio de trabajo y úsalo como base.
  • Copia contenido de la app al directorio de trabajo.
  • Arranca php-fpm.

Ahora nos toca construir las imágenes, algo que ya sabemos hacer de la anterior publicación. Recordad que la primera vez que se construyen tardan un poco más, pero cuando se cachean las “capas” irá todo mucho más rápido, siempre y cuando se haya seguido una buena estrategia de construcción del Dockerfile:

#Construir imagen Nginx
docker image build -t test-nginx -f Dockerfile .
#Construir imagen PHP-FPM
docker image build -t test-php-fpm -f DockerfilePHP .

A continuación usamos una ventana de la terminal para ejecutar los comandos de arranque de los contenedores correspondientes, uno por servicio:

#Arrancar contenedor PHP-FPM
docker container run --rm -it -v $PWD:/www/myapp --name test-php-fpm -p 9000:9000
test-php-fpm

Cuya salida por terminal será:

#Arrancar contenedor Nginx
docker container run --rm -it -v $PWD:/www/myapp --name test-nginx -p 8082:80
test-nginx

Que nos mostrará un maravilloso:

¡¿Qué ha pasado?! Mirad un momento el fichero /docker_assets/myapp.conf

server {
    listen       80;
    server_name  localhost;
    root         /www/myapp;

    index  index.html index.php index.htm;

    access_log  /var/log/nginx/access.log main;
    error_log   /var/log/nginx/error.log;
    location ~ \\.php$ {
        fastcgi_index   index.php;
        fastcgi_pass    test-php-fpm:9000;
        include         fastcgi_params;
        include         fastcgi.conf;
        fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
    }
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
}

Estamos diciéndole a Nginx que busque a PHP-FPM en test-php-fpm:9000. El puerto está bien, pero el nombre de dominio no. Hasta donde nosotros sabemos, no hemos configurado nada que nos permita localizar al contenedor por su nombre dentro de la red, así que tendremos que poner la IP del contenedor para que funcione la conexión. Para buscar la IP de un contenedor, haremos uso del comando docker container inspect test-php-fpm, cuya parte final del output debería ser algo así:

Ese comando nos está diciendo cosas muy interesantes:

  1. El contenedor está corriendo en una red interna llamada “bridge”, cuyo gateway es el 172.17.0.1
  2. La IP del contenedor es la 172.17.0.2

Con esto tenemos suficiente. Vamos al fichero /docker_assets/myapp.conf y cambiamos el test-php-fpm:9000 por 172.17.0.2, de manera que quede algo así:

server {
    listen       80;
    server_name  localhost;
    root         /www/myapp;

    index  index.html index.php index.htm;

    access_log  /var/log/nginx/access.log main;
    error_log   /var/log/nginx/error.log;
    location ~ \\.php$ {
        fastcgi_index   index.php;
        fastcgi_pass    172.17.0.2:9000;
        include         fastcgi_params;
        include         fastcgi.conf;
        fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
    }
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
}

Al cambiar el fichero de configuración de Nginx, debemos crear de nuevo la imagen y relanzar el contenedor de Nginx con el mismo comando de antes. Hecho esto podremos volver a http://localhost:8082/ y comprobar que algo ha cambiado.

Ahora mismo te puedes estar preguntando si cada vez que quieras lanzar contenedor vas a tener que estar pendiente de en qué IP está cada servicio. La respuesta es negativa. Docker usa redes internas para conectar los diferentes contenedores, y por defecto se lanzan en la red “bridge”. Si queremos usar beneficios como “la resolución de dominios”, tenemos que crear una red específica para lanzar nuestros contenedores. Con docker network help podréis ver las posibilidades.

Para crear una haremos lo siguiente:

docker network create mynet

Para ver el listado de redes disponibles, incluyendo la nueva, tendremos que ejecutar lo siguiente:

docker network ls

Para hacer uso de la nueva red pararemos los dos contenedores (Nginx y PHP-FPM), y volveremos a poner test-php-fpm:9000 en /docker_assets/myapp.conf. Acto seguido, crearemos de nuevo la imagen de Nginx con el comando de siempre. Ahora, vamos a arrancar los contenedores pero añadiendo una opción nueva. Os pongo los dos comandos de arranque:

#PHP-FPM
docker container run --rm -it -v $PWD:/www/myapp --name test-php-fpm -p 9000:9000 --net mynet test-php-fpm

#Nginx docker container run --rm -it -v $PWD:/www/myapp --name test-nginx -p 8082:80 --net mynet test-nginx

El --net mynet es nuestro nuevo amigo. Estamos arrancando los dos contenedores dentro de una misma red llamada mynet. Visitad http://localhost:8082/ y… ¡funciona sin estar gestionando IPs!

Por curiosidad, podéis ejecutar lo siguiente y fijaos en la red a la que pertenece ahora:

docker container inspect test-php-fpm

Llegados a este punto, tenéis a vuestra disposición las suficientes herramientas para “dockerizar” vuestros entornos. En la siguiente publicación añadiremos Redis como tercer y último servicio disponible sin usar Dockerfile, y trataremos la persistencia de datos para contenedores. ¡Hasta la próxima!

1 comment

  1. Berny
    octubre 13, 2020 at 4:55 pm

    Muchas gracias a todo el equipo de Product Hackers y especialmente al autor, Andrés Velasco. Soy programador junior que acabó sus estudios el pasado año y empezó a trabajar en una empresa muy pequeña como el único desarrollador, así que todo el aprendizaje ”corre de mi cuenta”. Ahora que tengo PHP bien asentado estaba buscando cosas nuevas que aprender y decidí tirarme a la aventura con Docker y empezar a aprender Angular/Node/Vue, de los que al final me decidí por el último. No quiero enrollarme mucho más pero no ha sido nada fácil encontrar guías o tutoriales con los que empezar, ya que casi siempre son muy ligeras o no explican bien los pasos a seguir o que estoy realizando en esos pasos. Y cuando estaba a punto de tirar la toalla y dejarlo por imposible me topé con tu primer artículo de esta ”serie” y que sorpresa me llevé al ver que había sido publicado unas semanas antes de que tomara mi decisión!! Tras una semana dando vueltas al tutorial y trasteando con el proyecto de git hoy he conseguido sacar una pequeña app mediante Docker en Nginx! Aún tengo que darle más vueltas al capítulo de Redis y espero con muchísimas ganas el de Composer!
    Muchas gracias a todos de nuevo, tenéis una web fantástica y unos artículos tan útiles como interesantes, espero que continuéis así!

Leave a Reply