Cómo desplegar un proyecto Symfony con Deployer

Hoy quiero haceros una breve introducción a una herramienta que descubrí a finales del año pasado y con la que estoy encantado gracias al trabajo que ahorra a la hora de desplegar un proyecto escrito en PHP (incluyendo Symfony, claro). Se trata de deployer cuyo enlace podéis visitar a continuación:

Así que en este artículo os hablaré de los primeros pasos para configurar esta herramienta de cara a que podáis usarla para automatizar los despliegues. ¡Vamos a ello!

Paso 1. Instalarla

El primer paso seguro que nadie se lo esperaba. Tened en cuenta que se puede instalar tanto de forma global como de manera local en nuestro proyecto (como si se tratase de otro paquete más en nuestro composer.json ).

Yo he escogido hacerlo a nivel local por la razón de poder emplear la librería de Symfony DotEnv para trabajar con variables de entorno como veréis a continuación. De hecho si trabajáis con Symfony os recomiendo que lo hagáis de este modo.

Una vez que lo hayáis hecho, deberéis de ejecutar la instrucción

php vendor/bin/dep init

para que os genere un archivo deploy.php en la raíz de vuestro proyecto. Este comando funciona mediante un wizard que os pregunta acerca del framework/CSM que estáis usando de cara a instalar las recipes correspondientes.

Paso 2. ¿Y qué son las recipes?

Las recipes de deployer son recetas que indican los pasos que hay que seguir para desplegar un proyecto según el framework/CMS que se está empleando.

Para uno escrito en Symfony seguramente os suenen estos pasos:

  • Hacer pull
  • Instalar los vendors
  • Migrar la base de datos a la versión actual
  • Instalar assets
  • Limpiar cachés
  • Generar un dump del autoload.php para optimizar la carga de ficheros

Pues básicamente esto es lo que viene configurado en la receta de deployer asociada a Symfony 4 como podéis ver en este enlace:

Así que si estáis trabajando con Symfony 4, aseguraros de que el archivo deploy.php generado tras ejecutar el comando dep init requiere la siguiente recipe:

require 'recipe/symfony4.php';

Paso 3. Configurando el servidor

Una vez que tengamos nuestro archivo deploy.php , lo siguiente será configurar nuestro servidor para adaptarlo a la forma en que deployer hace los despliegues.

Cuando ejecutamos deployer desde nuestro ordenador para hacer la subida, deployer crear 3 carpetas en el path del servidor le especifiquemos (en el siguiente punto os enseñaré a configurar dicho path).

  • La carpeta releases , donde se encuentran las últimas n versiones del proyecto por si hubiera que volver atrás tras un despliegue fallido (muy útil pero os podéis imaginar la cantidad de espacio que consume).
  • La carpeta current , la cual es un enlace simbólico a la última release subida.
  • La carpeta shared , donde se encuentran los archivos y carpetas que son compartidos por todas las releases (por ejemplo, la carpeta var o las variables de entorno)

Por tanto, será necesario que apuntemos nuestro servidor a donde se encuentre la carpeta current/public de cara a que nuestra aplicación funcione normalmente.

Paso 4. Configurando el archivo deploy.php

Hecho esto, lo siguiente será rellenar el archivo deploy.php con la información de nuestro servidor y proyecto para poder automatizar los despliegues.

Cómo especificar el repositorio

Para especificar el repositorio del cual descargar el proyecto en el servidor debéis modificar la siguiente instrucción:

set('repository', 'git@github.com:proyecto');

Cómo especificar el servidor donde desplegar

Deployer nos permite configurar múltiples servidores para realizar los despliegues. Esto se realiza mediante la siguiente instrucción:

host('user@domain.es')
->set('deploy_path', '/var/www');

Cómo especificar el número de releases a mantener en el servidor

Dado que mantener muchas releases anteriores puede suponer problemas de espacio, deployer nos da la opción de especificar el número de ellas que queremos mantener en el servidor mediante la siguiente línea:

set('keep_releases', 1);

Cómo especificar qué ficheros deben compartir todas las releases

Si queremos que ciertos archivos se compartan entre distintas relases lo podemos hacer añadiendo lo siguiente:

add('shared_files', ['file1', 'file2]);

Paso 5. Cómo ejecutar tests antes de cada despliegue

Si estáis escribiendo tests o trabajando con TDD, lo suyo sería ejecutar los tests antes de cada despliegue para confirmar que la nueva release pasa todos los tests que hayamos planteado no sea que…

Para ello, deployer nos da la opción de ejecutar tareas (llamadas taks) en cualquier momento del flujo que hayamos estipulado. Así que veamos como configurar el archivo deploy.php para ejecutar los tests antes de hacer cualquier otra cosa.

  • Lo primero será definir la siguiente task:
task('tests:run', function () {
  runLocally('bin/phpunit');
});

Esto define una tarea llamada tests:run que ejecuta localmente el comando bin/phpunit para correr los tests. En el caso de que falle la ejecución se detendrá y deployer no hará nada más.

  • A continuación, será necesario indicarle a deployer cuándo ejecutar dicha tarea. Dado que queremos que suceda antes que cualquier otra, escribiremos lo siguiente:
before('deploy', 'tests:run');

Y listo, con ello habremos configurado deployer para que nuestros tests se ejecuten nada más comenzar.

Paso 6. Cómo leer variables de entorno en el servidor mediate deployer

Este punto es bastante interesante ya que nos va a permitir profundizar más en el empleo de deployer.

Imaginad que por la razón que sea no podéis definir vuestras variables de entorno a nivel de servidor y tenéis que hacerlo a nivel local mediante un archivo .env en la raíz de vuestro proyecto. Es probable que en este caso, al ejecutar deployer os de un fallo al ejecutar php desde línea de comandos, ya que no tendrá las variables cargadas lo que puede suponer que por ejemplo no tengáis configurado el acceso a base de datos.

Es aquí donde entra una de las características más potentes de deployer y que es la posibilidad de ejecutar comandos en remoto a la vez que los procesamos en local. Veámoslo con un comando que carga las variables de entorno definidas en un archivo .env.local situado dentro de la carpeta shared generada por deployer:

use Symfony\Component\Dotenv\Dotenv;
...
task('load:env-vars', function () {
  $environment = run('cat {{deploy_path}}/.env.local');
  $dotenv = new Dotenv();
  $data = $dotenv->parse($environment);
  set('env', $data);
});
... 
after('tests:run', 'load:env-vars');

Como os comentaba al principio, puedo usar las librerías de mi carpeta vendor dentro de deploy.php (una de las ventajas de instalarlo a nivel local en nuestro proyecto), por lo que emplearé la librería Dotenv para procesar las variables de entorno

A continuación, defino una task llamada load:env-vars que al ejecutarse lanza el callback definido en el segundo argumento. En concreto este callback hace lo siguiente:

  1. Mediante la instrucción run ejecuto en remoto un cat del archivo y almaceno el resultado en la variable environment
  2. A continuación, proceso su contenido mediante la librería Dotenv y almaceno el resultado en la variable data
  3. Finalmente el comando set establece como variables de entorno el contenido de la variable data .

Por último, con la instrucción

after('tests:run', 'load:env-vars');

configuramos esta tarea para que se ejecute nada más correr los tests, de modo que los siguientes comandos que lance deployer en remoto tendrán ya las variables de entorno definidas en el archivo .env.local del servidor.

Nota sobre la instrucción task

La instrucción task puede escribirse sin necesidad de una callable en el segundo argumento, por ejemplo:

task('mycommand', 'bin/console mycommand')

Cuando la invocamos de este modo, deployer hace un cd al directorio de despliegue por lo que no es necesario que accedamos a él. No sucede así en el caso de que el segundo argumento sea un callable, de ahí que la instrucción que hacemos para, por ejemplo leer el archivo .env.local en el ejemplo anterior sea:

run('cat {{deploy_path}}/.env.local');

7. Y a despegar!

Digo… a desplegar, lo cual podéis hacer con el comando:

php vendor/bin/dep deploy

Si todo ha ido bien (que debería) se irán completando los pasos uno a uno hasta tener la nueva versión del proyecto instalada y configurada en el servidor.

Conclusiones

Como veis, deployer nos permite ahorrar bastante trabajo a la hora de desplegar los proyectos a la vez que nos da las suficientes herramientas para configurar y personalizar nuestros despliegues por lo que os recomiendo que le deis una oportunidad y me comentéis vuestras impresiones.

Hasta el siguiente artículo!